Problem: Need to format a BigDecimal through JSF, but JSF is destroying the precision of the BigDecimal.
JSF:
<h:outputText value="#{webUtilMB.roundUp(indexPrice.percentage, 2)}"/>
Java:
public class IndexPrice {
public BigDecimal getPercentage(){ return new BigDecimal("1.325"); }
}
@ManagedBean("webUtilMb")
public class WebUtilManagedBean{
public BigDecimal roundUp(BigDecimal dbvalue, int scale){
return dbvalue.setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP);
}
}
Having a break point in the WebUtilManagedBean.roundUp method showed me 'dbvalue' is '1.3249999999999999555910790149937383830547332763671875' and not '1.325'.
I then overloaded the roundUp method in WebUtilManagedBean with:
public Double roundUp(Double dvalue, int scale){
System.out.println(dvalue);
}
What surprised me when having a break point in this overloaded method was: - 'dvalue' is '1.325', which is correct. - the method actually got called instead of the roundUp(BigDecimal, int) method.
I later experimented with the BigDecimal constructor, with the following result:
BigDecimal db1 = new BigDecimal("1.325"); -> 1.325
BigDecimal db2 = new BigDecimal(1.325d); -> 1.3249999999999999555910790149937383830547332763671875
Theory: From the above, it seems that JSF was taking my BigDecimal value converting it to String, than to Double, than calling a 'new BigDecimal(double)' on the value to get the BigDecimal - which returns the wrong value.
Fix: One way to resolve this is to use the following code:
@ManagedBean("webUtilMb")
public class WebUtilManagedBean{
public Double roundUp(Double dvalue, int scale){
return this.roundUp(**new BigDecimal(dvalue.toString())**, BigDecimal.ROUND_HALF_UP);
}
public BigDecimal roundUp(BigDecimal dbvalue, int scale){
return dbvalue.setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP);
}
}
But that just seems like a hack to me.
Any ideas on fixing this and the reasons behind it. Thanks.
Ideally this would be done via f:convertNumber if it had a 'rounding' attribute, but it doesn't.
The clean way to do this would be to write your own roundup f:converter class, and use an f:converter tag where appropriate in the XHTML instead of using the EL method call. Your converter will get the value as an Object, i.e. a BigDecimal, and is responsible for converting it to a String itself. See the documentation for f:converter. Surprised that JSF doesn't do this correctly but I guess once you get into EL method calls everything is a String. Something like this (warning: untested):
import java.math.BigDecimal;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;
/**
* Use via e.g.:
* <pre>
* <h:outputText value="#{EL}">
* <f:converter id="com.edsi.jsf.RoundHalfUp"/>
* <f:attribute name="decimalPlaces" value="2"/>
* </h:outputText>
* <pre>
* @author Esmond Pitt
*/
@FacesConverter(value="com.edsi.jsf.RoundHalfUp")
public class RoundHalfUpConverter implements Converter
{
@Override
public Object getAsObject(FacesContext context, UIComponent component, String value)
{
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object value)
{
BigDecimal bd = (BigDecimal)value;
int decimalPlaces;
try
{
decimalPlaces = Integer.parseInt((String)component.getAttributes().get("decimalPlaces"));
}
catch (Exception exc)
{
decimalPlaces = 2; // or whatever
}
return bd.setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP).toString();
}
}