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

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.png

IEEE 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 余 0
2/2=1 余 0
1/2=0 余 1


小数部分0.25
0.25 2 = 0.5 未进位 0
0.50 2 = 1 进位整数 1


二进制表示:100.01
科学记数法表示:1.0001 * 2^2


转换为IEEE754格式存储:
符号位 0 (正数0 负数1)
指数 2 (float指数+127 double指数+1023)
尾数 0001


单精度float:符号位0 指数位129(10000001) 尾数001
0 10000001 00010000000000000000000


双精度double:符号位0 指数位1025(10000000001) 尾数001
0 10000000001 0001000000000000000000000000000000000000000000000000


不能精确表示的浮点数

举个例子:1/3,十进制就无法精确表示三分之一这个数字。


同样二进制也有很多很多小数无法精确表示,包括:0.1和0.2,这也是导致计算出现精度问题的根本原因。


下面将0.1和0.2转为2进制表示。


0.1

0.10 2 = 0.20 未进位 0
0.20 2 = 0.40 未进位 0
0.40 2 = 0.80 未进位 0
0.80 2 = 1.60 进位 1
0.60 2 = 1.20 进位 1
0.20 2 = 0.40 未进位 0
0.40 2 = 0.80 未进位 0
0.80 2 = 1.60 进位 1
0.60 2 = 1.20 进位 1
0.20 2 = 0.40 未进位 0
0.40 2 = 0.80 未进位 0
0.80 2 = 1.60 进位 1
0.60 2 = 1.20 进位 1
0.20 2 = 0.40 未进位 0
0.40 2 = 0.80 未进位 0
0.80 2 = 1.60 进位 1
0.60 2 = 1.20 进位 1
0.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 10011001100110011001101


double 双精度浮点数,尾数只能存储52位,多余位数四舍五入:
0 01111111011 1001100110011001100110011001100110011001100110011010


0.2

0.20 2 = 0.40 未进位 0
0.40 2 = 0.80 未进位 0
0.80 2 = 1.60 进位 1
0.60 2 = 1.20 进位 1
0.20 2 = 0.40 未进位 0
0.40 2 = 0.80 未进位 0
0.80 2 = 1.60 进位 1
0.60 2 = 1.20 进位 1
0.20 2 = 0.40 未进位 0
0.40 2 = 0.80 未进位 0
0.80 2 = 1.60 进位 1
0.60 2 = 1.20 进位 1
0.20 2 = 0.40 未进位 0
0.40 2 = 0.80 未进位 0
0.80 2 = 1.60 进位 1
0.60 2 = 1.20 进位 1
0.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 10011001100110011001101


double 双精度浮点数,尾数只能存储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
尾数:00110011001100110011010
ieee754: 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
尾数:0011001100110011001100110011001100110011001100110100
ieee754: 0 01111111101 0011001100110011001100110011001100110011001100110100
转换为十进制数:0.3000000000000000444089201865780
转换为double结果:0.30000000000000004



image.png
浮点数计算

要避免浮点数计算问题,可以通过BigDecimal来计算。


System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2")));//输出:0.3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值