利用润乾自带显示格式所带来的四舍六入五成双问题

第55章 利用润乾自带显示格式所带来的四舍六入五成双问题

1. 问题概述
在润乾V4版本中使用显示格式:#,对单元格中的值进行四舍五入时会有一个问题:单元格中写了一个小数,如4.5,设置其显示格式为:#,预览的结果是4;若将4.5改成4.6或者4.51,设置显示格式为#,预览结果为5。遇到这种情况我们通常都会想到可以利用润乾自带的函数round()进行四舍五入的处理,
但如果客户的数据是利用自动计算里得到的,那就不能使用round() 来完成了,而用显示格式#,就会带来这样的问题,咨询研发得知,报表在显示格式化的时候利用的是:
NumberFormat numberFormat = new DecimalFormat(“#0.##”); 的java规则:
四舍六入,奇数入,偶数不入。
即数据”3.5″设置其显示格式为#,则得到的结果是4
数据”4.5″设置其显示格式为#,则得到的结果也是4

其实很多语言都使用的”四舍六入”的规则。 从统计学的角度,”四舍六入五成双”比”四舍五入”要科学,在大量运算时,它使舍入后的结果误差的均值趋于零,而不是像四舍五入那样逢五就入,导致结果偏向大数,使得误差产生积累进而产生系统误差,”四舍六入五成双”使测量结果受到舍入误差的影响降到最低。

其实这并不是我们的BUG,所有java的各个版本JDK均使用此规则。四舍五入还是”六入五成双”,不是简单的哪个更合理的问题。因为客户有它的客户,每个客户有自己内部一套规则。而且四舍五入恐怕对绝大多数人都是习以为常的。
2. 解决方案
功能改进贡献者:郑谷川,周辉辉,周俊,蔡博文,此次向大家介绍一下.
方法A是变通方式不推荐,方法B是重点:

A:虽然客户的数据是利用自动计算等到的,其实也可以利用变通的方式来实现: 自动计算里是可以直接调用JS,利用JS实现精确四舍五入即可,具体写法:NumberObject.toFixed(num),num为必需,规定小数的位数:



虽然此方式可以解决,但写法比较繁琐,客户往往希望有一个简单的设置就做到了四舍五入,那就是利用设置显示格式的方式.

方法B:
1.由于报表在显示格式化的时候利用的是:
NumberFormat numberFormat = new DecimalFormat(“#0.##”); 在看过DecimalFormat源码的基础上,覆盖JAVA原有DecimalFormat的format方法,在实际进行格式化前,先对数值做好四舍五入。
2.由于显示格式里没有接口,经过和研发讨论之后,增加数值显示格式化自定义类配置项,可自行实现格式化规则.


4. 在设计器里一旦启用了数值格式化类名:填写自定义格式化类,显示格式的数值型就会利用自定义格式化规则,原本数据格式化将会失效:


5. 在设计器里具体用法,想写好的自定义格式化JAVA文件编译之后放入:
E:\reportHome\designer\web\WEB-INF\classes
在B/S模式上使用时:需要在reportConfig.xml中增加配置:
<config>
<name>numberFormatClass</name>
<value>expression.NewObjectFormat</value>
</config>

更新report4.jar

3. 资料说明

在解决四舍五入时,几经周折:
1.根据查询了相关资料得知,float和double型的底层实现是二进制的。十进制中的一个有限位数小数,转换成二进制就不一定是有限位数了,一旦位数超过的float和double型的位数宽度,就会出现”精度溢出”。所以float和double型是为了科学计算而设计的,并不适合精确的十进制计算.
2.Java中的浮点数类型的float和double是不能够用来进行资金的运算。在《Effective Java》中也提到这个原则:float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用java.math.BigDecimal。利用BigDecimal重新实现四舍五入部分,关键代码:
BigDecimal b = new BigDecimal(Double.toString(v));BigDecimal one = new BigDecimal(“1″);return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
BigDecimal.ROUND_HALF_UP,向”最接近的”数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。
4. 程序说明

package expression;

import java.math.BigDecimal;

import java.text.DecimalFormat;

import java.text.FieldPosition;

public class NewObjectFormat extends DecimalFormat {

private static final long serialVersionUID = 1L;

/** 要舍入的数位:个位为0,十分位为1,依次类推…… */

private int roundDigit = 0;

public NewObjectFormat(String pattern) {

super(pattern);

roundDigit = getRoundDigit(pattern);

System.out.println(“roundDigit:” + roundDigit);

}

/**

* 计算需要舍入的数位

*

* @param pattern

* @return

*/

private int getRoundDigit(String pattern) {

int digit = 0;

// 计算需要截取的位数

int index = pattern.indexOf(‘.’);

System.out.println(“INDEX:” + index);

if (index >= 0) {

for (int i = index + 1; i < pattern.length(); i++) {

char c = pattern.charAt(i);

System.out.println(“C:” + c);

if (c == ’0′ || c == ‘#’)

digit++;

else

break;

}

}

// 判断是否有百分号

if (pattern.endsWith(“%”)) {

digit += 2;

}

return digit;

}

@Override

public StringBuffer format(double number, StringBuffer result,

FieldPosition fieldPosition) {

// 将数字修正后调用父方法

return super.format(fixNumber(number), result, fieldPosition);

}

@Override

public StringBuffer format(long number, StringBuffer result,

FieldPosition fieldPosition) {

// 将数字修正后调用父方法

return super.format(fixNumber(number), result, fieldPosition);

}

private double fixNumber(double number) {

boolean isNegative = number < 0;

// 取数字的绝对值,并进行四舍五入

double n = round(Math.abs(number), roundDigit);

System.out.println(“N:” + n);

// 返回修正后的数字

return isNegative ? (0 – n) : n;

}

/**

* 提供精确的小数位四舍五入处理

*

* @param v

*需要四舍五入的数字

* @param scale

*小数点后保留几位

* @return 四舍五入后的结果,

*BigDecimal.ROUND_HALF_UP,最接近的数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。

*/

public static double round(double v, int scale) {

if (scale < 0) {

throw new IllegalArgumentException(

“The scale must be a positive integer or zero”);

}

BigDecimal b = new BigDecimal(Double.toString(v));

BigDecimal one = new BigDecimal(“1″);

return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();

}

public static void main(String[] args) {

double num = 4.445;

DecimalFormat df = new NewObjectFormat(“#.##”);

System.out.println(“Result:”+df.format(num));

}

}

热门文章