JAVA浮点数计算精度损失底层原理与解决方案
问题:
对两个double类型的值进行运算,有时会出现结果值异常的问题。比如:
system.out.println(19.99+20); system.out.println(1.0-0.66); system.out.println(0.033*100); system.out.println(12.3/100);
输出:
39.989999999999995 0.33999999999999997 3.3000000000000003 0.12300000000000001
java中的简单浮点数类型float和double不能够精确运算。这个问题其实不是java的bug,因为计算机本身是二进制的,而浮点数实际上只是个近似值,所以从二进制转化为十进制浮点数时,精度容易丢失,导致精度下降。
关于精度损失的原理可以很简单的讲,首先一个正整数在计算机中表示使用01010形式表示的,浮点数也不例外。
比如11,11除以2等于5余1
5除以2等于2余1
2除以2等于1余0
1除以2等于0余1
所以11二进制表示为:1011.
double类型占8个字节,64位,第1位为符号位,后面11位是指数部分,剩余部分是有效数字。
正整数除以2肯定会有个尽头的,之后二进制还原成十进制只需要乘以2即可。
举个例子:0.99用的有效数字部分,
0.99 * 2 = 1+0.98 --> 1
0.98 * 2 = 1+0.96 --> 1
0.96 * 2 = 1+0.92 -- >1
0.92 * 2 = 1+0.84 -- >1
...............
这样周而复始是没法有尽头的,而double有效数字有限,所以必定会有损失,所以二进制无法准确表示0.99,就像十进制无法准确表示1/3一样。
解决办法:
在《effective java》中提到一个原则,那就是float和double只能用来作科学计算或者是工程计算,但在商业计算中我们要用java.math.bigdecimal,通过使用bigdecimal类可以解决上述问题,首先需要注意的是,直接使用字符串来构造bigdecimal是绝对没有精度损失的,如果用double或者把double转化成string来构造bigdecimal依然会有精度损失,所以我觉得这种解决方法就是在使用中就把浮点数用string来表示存放,涉及到运算直接用string构造double,否则肯定会有精度损失。
1. 相加
/** * 相加 * @param double1 * @param double2 * @return */ public static double add(string doublevala, string doublevalb) { bigdecimal a2 = new bigdecimal(doublevala); bigdecimal b2 = new bigdecimal(doublevalb); return a2.add(b2).doublevalue(); }
2. 相减
/** * 相减 * @param double1 * @param double2 * @return */ public static double sub(string doublevala, string doublevalb) { bigdecimal a2 = new bigdecimal(doublevala); bigdecimal b2 = new bigdecimal(doublevalb); return a2.subtract(b2).doublevalue(); }
3. 相乘
/** * 相乘 * @param double1 * @param double2 * @return */ public static double mul(string doublevala, string doublevalb) { bigdecimal a2 = new bigdecimal(doublevala); bigdecimal b2 = new bigdecimal(doublevalb); return a2.multiply(b2).doublevalue(); }
4. 相除
/** * 相除 * @param double1 * @param double2 * @param scale 除不尽时指定精度 * @return */ public static double div(string doublevala, string doublevalb, int scale) { bigdecimal a2 = new bigdecimal(doublevala); bigdecimal b2 = new bigdecimal(doublevalb); return a2.divide(b2, scale, bigdecimal.round_half_up).doublevalue(); }
5. 主函数调用
public static void main(string[] args) { string doublevala = "3.14159267"; string doublevalb = "2.358"; system.out.println("add:" + add(doublevala, doublevalb)); system.out.println("sub:" + sub(doublevala, doublevalb)); system.out.println("mul:" + mul(doublevala, doublevalb)); system.out.println("div:" + div(doublevala, doublevalb, 8)); }
结果展示如下所示:
add:5.49959267 sub:0.78359267 mul:7.40787551586 div:1.33231241
所以最好的方法是完全抛弃double,用string和java.math.bigdecimal。
java遵照ieee制定的浮点数表示法来进行float,double运算。这种结构是一种科学计数法,用符号、指数和尾数来表示,底数定为2——即把一个浮点数表示为尾数乘以2的指数次方再添上符号。具体底层如何存储以及如何进行运行请继续关注我的博客,后续我会将详情总结好的。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!
下一篇: Android仿微信5实现滑动导航条