您的位置:首页 > 编程语言 > Java开发

Java浮点数计算精度问题总结

2017-10-11 14:16 302 查看
Java浮点数计算精度问题总结首先看看下面两个简单计算分别会输出什么样的结果,如果你觉得都会输出0.3,那么你应该耐心看完本文。
System.out.println(0.1 + 0.2);  //输出:0.30000000000000004
System.out.println(0.1f + 0.2f);//输出:0.3
本文讨论下面这些问题:为什么浮点数计算会存在精度问题?为什么相同的两个数字相加,float和double计算的结果不一致?如何避免精度问题?浮点数标准首先简单了解一下浮点数标准,java中浮点数采用的IEEE754标准,该标准的全称为IEEE二进制浮点数算术标准。存储格式:符号位+指数位偏移+尾数位image.pngIEEE 754常用的两种表示浮点数值的方式:单精确度(float 32位)、双精确度(double 64位)image.png规约形式的浮点数:如果浮点数中指数部分的编码值在0 < exponent < 2e-2之间,且尾数部分最高有效位(即整数字)是1,那么这个浮点数将被称为规约形式的浮点数。“规约”是指用唯一确定的浮点形式去表示一个值。由于这种表示下的尾数有一位隐含的二进制有效数字,为了与二进制科学计数法的尾数相区别,IEEE754称之为有效数(significant)。非规约形式的浮点数:如果浮点数的指数部分的编码值是0,尾数为非零,那么这个浮点数将被称为非规约形式的浮点数。IEEE 754标准规定:非规约形式的浮点数的指数偏移值比规约形式的浮点数的指数偏移值大1.例如,最小的规约形式的单精度浮点数的指数部分编码值为1,指数的实际值为-126;而非规约的单精度浮点数的指数域编码值为0,对应的指数实际值也是-126而不是-127。实际上非规约形式的浮点数仍然是有效可以使用的,只是它们的绝对值已经小于所有的规约浮点数的绝对值;即所有的非规约浮点数比规约浮点数更接近0。规约浮点数的尾数大于等于1且小于2,而非规约浮点数的尾数小于1且大于0.精度在二进制,第一个有效数字必定是“1”,因此这个“1”并不会存储。单精和双精浮点数的有效数字分别是有存储的23和52个位,加上最左手边没有存储的第1个位,即是24和53个位。浮点数的比较浮点数基本上可以按照符号位、指数域、尾数域的顺序作字典比较。显然,所有正数大于负数;正负号相同时,指数的二进制表示法更大的其浮点数值更大。浮点数转二进制数能精确表示的浮点数哪些小数能被精确表示呢?0.5的倍数,且在精度以内。方便计算,首先选择可以用浮点数精确表示的数计算:4.25首先将数字转为2进制:整数部分4:4/2=2 余 02/2=1 余 01/2=0 余 1小数部分0.250.25 2 = 0.5 未进位 00.50 2 = 1 进位整数 1二进制表示:100.01科学记数法表示:1.0001 * 2^2转换为IEEE754格式存储:符号位 0 (正数0 负数1)指数 2 (float指数+127 double指数+1023)尾数 0001单精度float:符号位0 指数位129(10000001) 尾数0010 10000001 00010000000000000000000双精度double:符号位0 指数位1025(10000000001) 尾数0010 10000000001 0001000000000000000000000000000000000000000000000000不能精确表示的浮点数举个例子:1/3,十进制就无法精确表示三分之一这个数字。同样二进制也有很多很多小数无法精确表示,包括:0.1和0.2,这也是导致计算出现精度问题的根本原因。下面将0.1和0.2转为2进制表示。0.10.10 2 = 0.20 未进位 00.20 2 = 0.40 未进位 00.40 2 = 0.80 未进位 00.80 2 = 1.60 进位 10.60 2 = 1.20 进位 10.20 2 = 0.40 未进位 00.40 2 = 0.80 未进位 00.80 2 = 1.60 进位 10.60 2 = 1.20 进位 10.20 2 = 0.40 未进位 00.40 2 = 0.80 未进位 00.80 2 = 1.60 进位 10.60 2 = 1.20 进位 10.20 2 = 0.40 未进位 00.40 2 = 0.80 未进位 00.80 2 = 1.60 进位 10.60 2 = 1.20 进位 10.20 2 = 0.40 未进位 0无限循环...二进制表示0.1:0.00011001100110011001100110011001100110011001100110011001...科学记数表示:1.1001100110011001100110011001100110011001100110011001... * 2^-4转换为IEEE754格式存储:符号位 0 (正数0 负数1)指数 -4 (float指数+127 double指数+1023)尾数 1001100110011001100110011001100110011001100110011001...float 单精度浮点数,尾数只能存储23位,多余位数四舍五入:0 01111011 10011001100110011001101double 双精度浮点数,尾数只能存储52位,多余位数四舍五入:0 01111111011 10011001100110011001100110011001100110011001100110100.20.20 2 = 0.40 未进位 00.40 2 = 0.80 未进位 00.80 2 = 1.60 进位 10.60 2 = 1.20 进位 10.20 2 = 0.40 未进位 00.40 2 = 0.80 未进位 00.80 2 = 1.60 进位 10.60 2 = 1.20 进位 10.20 2 = 0.40 未进位 00.40 2 = 0.80 未进位 00.80 2 = 1.60 进位 10.60 2 = 1.20 进位 10.20 2 = 0.40 未进位 00.40 2 = 0.80 未进位 00.80 2 = 1.60 进位 10.60 2 = 1.20 进位 10.20 2 = 0.40 未进位 0无限循环...二进制表示0.2:0.00110011001100110011001100110011001100110011001100110011...科学记数表示:1.10011001100110011001100110011001100110011001100110011... 2^-3转换为IEEE754格式存储:符号位 0 (正数0 负数1)指数 -3 (float指数+127 double指数+1023)尾数 10011001100110011001100110011001100110011001100110011...float 单精度浮点数,尾数只能存储23位,多余位数四舍五入:0 01111100 10011001100110011001101double 双精度浮点数,尾数只能存储52位,多余位数四舍五入:0 01111111100 1001100110011001100110011001100110011001100110011010二进制浮点数相加小数点对其,两数相加。单精度浮点数:0.1f + 0.2f
1.10011001100110011001101 2^-4
+ 11.00110011001100110011010 2^-4
=100.11001100110011001100111 2^-4
=  1.00110011001100110011010 2^-2
=  0.0100110011001100110011010 *
计算结果:符号位:0指数位:-2+127 = 125尾数:00110011001100110011010ieee754: 0 01111101 00110011001100110011010转换为十进制数:0.300000011920928955078125转为float结果:0.3双精度浮点数:0.1 + 0.2
1.1001100110011001100110011001100110011001100110011010 2^-4
+ 11.0011001100110011001100110011001100110011001100110100 2^-4
=100.1100110011001100110011001100110011001100110011001110 2^-4
=  1.0011001100110011001100110011001100110011001100110100 2^-2
=  0.010011001100110011001100110011001100110011001100110100 *
计算结果:符号位:0指数位:-2+1023 = 1021尾数:0011001100110011001100110011001100110011001100110100ieee754: 0 01111111101 0011001100110011001100110011001100110011001100110100转换为十进制数:0.3000000000000000444089201865780转换为double结果:0.30000000000000004image.png浮点数计算要避免浮点数计算问题,可以通过BigDecimal来计算。
System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2")));//输出:0.3

                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: