1.发现问题
数据库表有2个 int 类型字段,现在需要合并2个字段保留2位小数显示。
之前只展示一个字段的时候,我们的做法如下:
<fmt:formatNumber pattern="0.00" value="${A / 100}"/>
当 A = 50 页面显示结果 0.50
现在查询的时候 将2个int 字段 合并查询以 select (A+B) as C 方式 :
<fmt:formatNumber pattern="0.00" value="${C / 100}"/>
当 C = 50 页面显示结果 1.00
2.定位问题
将后台值直接在页面返回,显示没错:
通过在页面直接返回:
${C}
当 C = 50 没错
直接在JSP页面上手动赋值,正常显示:
%
Integer A = 50;
request.setAttribute("A", A);
%>
<fmt:formatNumber pattern="0.00" value="${A / 100}"/>
页面显示 0.50
手动赋值没问题,首先排除问题fmt 格式化问题,因为一直都是这么格式化。
项目其他都没改,只是在SQL中合并了2个字段返回,开始猜想应该是合并返回的导致的问题。
立马调试代码,项目技术使用: SpringBoot + JPA ,查询结果未做转化,直接返回前端
通过调试基本确定问题原因 :数据库中2个int 字段累加返回 在java中自动会变为 BigInteger.
进一步确认问题,手动在页面进行测试:
<%
BigInteger A = BigInteger.valueOf(50);
request.setAttribute("A", A);
%>
<fmt:formatNumber pattern="0.00" value="${A / 100}"/>
页面显示 1.00
显示结果有误,问题完全定位。
3. 跟踪EL表达式源码,进一步了解问题原因
通过在tomcat/lib 下 el 依赖包抽取几个核心类:
下面列出几个进行除法运算的类:
AstDiv.java
//EL 表达式2个数相除
public final class AstDiv
{
public AstDiv(){
}
public Object getValue(Object obj0, Object obj1)
{
return ELArithmetic.divide(obj0, obj1);
}
}
ELArithmetic.java 计算核心类
/**
* A helper class of Arithmetic defined by the EL Specification
* @author Jacob Hookom [jacob@hookom.net]
*/
public abstract class ELArithmetic {
public static final BigDecimalDelegate BIGDECIMAL = new BigDecimalDelegate();
public static final BigIntegerDelegate BIGINTEGER = new BigIntegerDelegate();
public static final DoubleDelegate DOUBLE = new DoubleDelegate();
public static final LongDelegate LONG = new LongDelegate();
//除法
public static final Number divide(final Object obj0, final Object obj1) {
if (obj0 == null && obj1 == null) {
return ZERO;
}
final ELArithmetic delegate;
if (BIGDECIMAL.matches(obj0, obj1)) //判断2个参数是否有一个为BigDecimal类型,如果存在就委托 BigDecimalDelegate 计算
delegate = BIGDECIMAL;
else if (BIGINTEGER.matches(obj0, obj1)) //判断2个参数是否有一个为BIGINTEGER类型,如果存在就委托 BigDecimalDelegate 计算
delegate = BIGDECIMAL;
else
delegate = DOUBLE; //否则就委托 DoubleDelegate计算
Number num0 = delegate.coerce(obj0);
Number num1 = delegate.coerce(obj1);
return delegate.divide(num0, num1);
}
public static final class BigIntegerDelegate extends ELArithmetic {
@Override
protected Number add(Number num0, Number num1) {
return ((BigInteger) num0).add((BigInteger) num1);
}
@Override
protected Number coerce(Number num) {
if (num instanceof BigInteger)
return num;
return new BigInteger(num.toString());
}
@Override
protected Number coerce(String str) {
return new BigInteger(str);
}
//
@Override
protected Number divide(Number num0, Number num1) {
return (new BigDecimal((BigInteger) num0)).divide(new BigDecimal((BigInteger) num1), BigDecimal.ROUND_HALF_UP);
}
@Override
protected Number multiply(Number num0, Number num1) {
return ((BigInteger) num0).multiply((BigInteger) num1);
}
@Override
protected Number mod(Number num0, Number num1) {
return ((BigInteger) num0).mod((BigInteger) num1);
}
@Override
protected Number subtract(Number num0, Number num1) {
return ((BigInteger) num0).subtract((BigInteger) num1);
}
@Override
public boolean matches(Object obj0, Object obj1) {
return (obj0 instanceof BigInteger || obj1 instanceof BigInteger);
}
}
}
当参数存在BigInteger 会委托 BigDecimalDelegate 进行计算,我们来看看关键计算除法的代码:
public static final class BigDecimalDelegate extends ELArithmetic {
//加法
@Override
protected Number add(Number num0, Number num1) {}
@Override
protected Number coerce(Number num) {}
@Override
protected Number coerce(String str) {}
//除法 关键:BigDecimal.ROUND_HALF_UP, 会对结果四舍五入 当结果小数位>=0.5,会入1
@Override
protected Number divide(Number num0, Number num1) {
return ((BigDecimal) num0).divide((BigDecimal) num1,
BigDecimal.ROUND_HALF_UP);
}
//减法
@Override
protected Number subtract(Number num0, Number num1) {}
@Override
protected Number mod(Number num0, Number num1) {}
@Override
protected Number multiply(Number num0, Number num1) {}
@Override
public boolean matches(Object obj0, Object obj1) {
return (obj0 instanceof BigDecimal || obj1 instanceof BigDecimal);
}
}
问题原因:关键:BigDecimal.ROUND_HALF_UP, 会对结果四舍五入 当结果小数位>=0.5,会入1
代码测试:
/**
* Created by rain.wen on 2017/6/2.
*/
public class Test {
public static void main(String[] args) {
Test.testLongDiv();
Test.testOneBigIntegerDiv();
}
public static void testLongDiv(){
AstDiv astDiv = new AstDiv();
Object obj0 = 50L;
Object obj1 = 100L;
System.out.println(" 50L/100L = " + astDiv.getValue(obj0, obj1));
}
public static void testOneBigIntegerDiv(){
AstDiv astDiv = new AstDiv();
Object obj0 = BigInteger.valueOf(50L);
Object obj1 = 100L;
System.out.println(" BigInteger.valueOf(50L)/100L = " + astDiv.getValue(obj0, obj1));
}
}
结果:
代码地址:https://git.oschina.net/rainwen/my-web.git
参考:http://jinnianshilongnian.iteye.com/blog/1869706