对于“DecimalFormat和BigDecimal小数点的四舍五入陷阱”的修改

来源:互联网 发布:windows phone软件 编辑:程序博客网 时间:2024/06/05 14:35

decimalformat是很好用, 本人也习惯性的用它, 直到最近, 在做有关财务发票的内容时, 才发现其中的一个陷阱(不是BUG) 想要用decimalformat做小数点后2位,一般做法都是new DecimalFormat("0.00"); 这样就可以得到一个格式化好的小数后2位数值, 而且"好像"还自动四舍五入了 其实不然, 当小数只有3位时, 第三位又为5时, 并不会自己进1, 即 245.205 -> 245.20, 而非我们所期望的 245.21 要得到可靠的小数后2位四舍五入, 还是推荐BigDecimal. 但几天后, 突然发现BigDecimal也不可靠了(72.675小数2位没有变成72.68) 修改了测试代码, 结果令人纳闷, 其原因是: 小数转二进制时, 生成无限循环, 像1.1*100时的0.1 Java代码 package com.ys.util; import java.math.BigDecimal; import java.text.DecimalFormat; import junit.framework.TestCase; public class NumberTest extends TestCase { public void test1() { System.out.println("test BigDecimal & DecimalFormat"); Double oriValue=245.205; BigDecimal bd=new BigDecimal(oriValue).setScale(2, BigDecimal.ROUND_HALF_UP); System.out.println(); DecimalFormat df2=new DecimalFormat("0.00"); String text=String.format("original : %s, BigDecimal : %s, DecimalFormat : %s", oriValue, bd, df2.format(oriValue)); System.out.println(text); oriValue=72.675; bd=new BigDecimal(oriValue).setScale(2, BigDecimal.ROUND_HALF_UP); System.out.println(); text=String.format("original : %s, BigDecimal : %s, DecimalFormat : %s", oriValue, bd, df2.format(oriValue)); System.out.println(text); oriValue=172.675; bd=new BigDecimal(oriValue).setScale(2, BigDecimal.ROUND_HALF_UP); System.out.println(); text=String.format("original : %s, BigDecimal : %s, DecimalFormat : %s", oriValue, bd, df2.format(oriValue)); System.out.println(text); assertTrue(true); } } package com.ys.util; import java.math.BigDecimal; import java.text.DecimalFormat; import junit.framework.TestCase; public class NumberTest extends TestCase { public void test1() { System.out.println("test BigDecimal & DecimalFormat"); Double oriValue=245.205; BigDecimal bd=new BigDecimal(oriValue).setScale(2, BigDecimal.ROUND_HALF_UP); System.out.println(); DecimalFormat df2=new DecimalFormat("0.00"); String text=String.format("original : %s, BigDecimal : %s, DecimalFormat : %s", oriValue, bd, df2.format(oriValue)); System.out.println(text); oriValue=72.675; bd=new BigDecimal(oriValue).setScale(2, BigDecimal.ROUND_HALF_UP); System.out.println(); text=String.format("original : %s, BigDecimal : %s, DecimalFormat : %s", oriValue, bd, df2.format(oriValue)); System.out.println(text); oriValue=172.675; bd=new BigDecimal(oriValue).setScale(2, BigDecimal.ROUND_HALF_UP); System.out.println(); text=String.format("original : %s, BigDecimal : %s, DecimalFormat : %s", oriValue, bd, df2.format(oriValue)); System.out.println(text); assertTrue(true); } } 输出结果: test BigDecimal & DecimalFormat original : 245.205, BigDecimal : 245.21, DecimalFormat : 245.20 original : 72.675, BigDecimal : 72.67, DecimalFormat : 72.68 original : 172.675, BigDecimal : 172.68, DecimalFormat : 172.68 自己写的算法 Java代码 package com.ys.util; import java.math.BigDecimal; public class MathUtil { /** * round double value * @param doubleValue * @param scale * @return */ public static Double round(Double doubleValue, int scale){ Double flag=null; StringBuffer sb=new StringBuffer("0."); for(int i=0;i 505.99999999999994 小数转二进制不能被整除引起 flag=new BigDecimal(flag*pow).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue(); flag=Math.floor(flag); flag=flag/pow; return flag; } } package com.ys.util; import java.math.BigDecimal; public class MathUtil { /** * round double value * @param doubleValue * @param scale * @return */ public static Double round(Double doubleValue, int scale){ Double flag=null; StringBuffer sb=new StringBuffer("0."); for(int i=0;i 505.99999999999994 小数转二进制不能被整除引起 flag=new BigDecimal(flag*pow).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue(); flag=Math.floor(flag); flag=flag/pow; return flag; } } 输出结果 Math Round Calculate origin : 245.205, round : 245.21 origin : 72.675, round : 72.68 origin : 172.675, round : 172.68 这个算法的原理很简单, 就是把小数对应的小数位+5, 然后乘10的scale次方, 然后把小位数去掉, 再除以10的scale次方原来的小数位 从注释的//flag=flag*pow; //5.06*100 -> 505.99999999999994 可以看出, java在做浮点运算时, 有带尾数的偏向, 而那0.5没有进一的差距, 也就是在这里产生 算法二, 用String处理, 不需要依赖Math Java代码 /** * round double value * @param doubleValue * @param scale * @return */ public static Double round(Double doubleValue, int scale){ Double flag=null; StringBuffer ori=new StringBuffer(doubleValue.toString()); int index=ori.indexOf("."); if(index>0) ori.deleteCharAt(index); //remove dot ori.delete(index+scale+1, ori.length()); //offset for scale, (5.055, 2)->(5055), scale+1, for +5 Integer value=new Integer(ori.toString()); value+=5; //round StringBuffer sb=new StringBuffer(value.toString()); index=sb.indexOf("."); if(index>0) sb.delete(index, sb.length()); sb.deleteCharAt(sb.length()-1); sb.insert(sb.length()-scale, '.'); //restore dot flag=new Double(sb.toString()); return flag; } /** * round double value * @param doubleValue * @param scale * @return */ public static Double round(Double doubleValue, int scale){ Double flag=null; StringBuffer ori=new StringBuffer(doubleValue.toString()); int index=ori.indexOf("."); if(index>0) ori.deleteCharAt(index); //remove dot ori.delete(index+scale+1, ori.length()); //offset for scale, (5.055, 2)->(5055), scale+1, for +5 Integer value=new Integer(ori.toString()); value+=5; //round StringBuffer sb=new StringBuffer(value.toString()); index=sb.indexOf("."); if(index>0) sb.delete(index, sb.length()); sb.deleteCharAt(sb.length()-1); sb.insert(sb.length()-scale, '.'); //restore dot flag=new Double(sb.toString()); return flag; }

以上方法中需要增加对整数的判断,否则如果整数调用此方法会报错

if((index+scale+1)>=sb.length()){
                 return valueFloat;
             }

 JAVA官方的解决办法 Java代码 public static Double round(Double doubleValue, int scale){ Double flag=null; String text=doubleValue.toString(); BigDecimal bd=new BigDecimal(text).setScale(scale, BigDecimal.ROUND_HALF_UP); flag=bd.doubleValue(); return flag; } public static Double round(Double doubleValue, int scale){ Double flag=null; String text=doubleValue.toString(); BigDecimal bd=new BigDecimal(text).setScale(scale, BigDecimal.ROUND_HALF_UP); flag=bd.doubleValue(); return flag; } 用String生成BigDecimal就可以了

原创粉丝点击