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

JAVA问题分析——浮点型为什么会存在误差

2020-08-29 12:23 531 查看

文章目录


在编程中,浮点型(float,double)通常用来表示小数,我们经常被警告,浮点型存在精度问题,不能用来比较大小,不能用来表示金额,为什么会存在误差呢?首先,我们来了解一下浮点型在计算机内存中是如何存储的。

小数位用二进制表示

要表示浮点型,首先要让小数位也用二进制表示。我们知道二进制表示整数时,最低位代表2的0次方,往高位依次是2的1次方,2次方,3次方……那么对应的,二进制数小数点后面,最高位则是2的-1次方,-2次方,-3次方……如下图所示:

如:

十进制 二进制 计算方式
5.5 101.1 22+20+2-1
8.625 1000.101 23+2-1+2-3
1.6 1.10011001…… 20+2-1+2-4+2-5+2-8+……

由此可见,用二进制数表示小数,可能会存在一个非常大的问题:一些本来不是无限循环的十进制小数,表示成二进制之后成了无限循环小数。如0.6用二进制表示就会成为一个无限循环的小数。
计算机无法存储一个无线循环的数,只能将这个数零舍一入(二进制版的四舍五入),这是浮点数存在误差的根本原因。

二进制科学计数法表示

将浮点型用二进制表示后,下一步要将浮点数用科学计数法表示:

十进制 二进制 二进制科学计数法表示
5.5 101.1 1.011*22
8.625 1000.101 1.000101*23
0.6 0.10011001…… 1.0011001……*2-1

对于任何数字表示成二进制科学计数法以后,一定是1点几(尾数)乘以2的多少次方(指数)。对于小于零的负数来说,就是负1点几(尾数)乘以2的多少次方(指数)。
所以要存这个数,需要存储三个部分:正负号,尾数,指数

浮点型的内存结构

于是,浮点型的内存结构包含了三个部分,S(正负号),E(指数加偏移量),M(尾数)。如图所示:

最高位有1bit存储正负号,然后指数部分占据8bits(4字节)或11bits(8字节),其余部分全都用来存储尾数部分。对于指数部分,这里存储的结果是实际的指数加上偏移量之后的结果。这里设置偏移量,是为了让指数部分不出现负数,全都为大于等于0的正整数。尾数部分的存储,因为二进制的科学计数法,小数点前一定是1开头,因此我们尾数只需要存储小数点后面的部分即可。
如浮点数0.6,它的内存结构为:

浮点型取值范围和精度

知道了浮点型的内存结构,我们不难发现,浮点型的取值范围是由指数来决定的:
float的范围为-2128 ~ +2127,也即-3.40E+38 ~ +3.40E+38;double的范围为-21024 ~ +21023,也即-1.79E+308 ~ +1.79E+308。
浮点型的误差是由尾数的位数来决定的:
浮点数在内存中是按科学计数法来存储的,其整数部分始终是一个隐含着的“1”,由于它是不变的,故不能对精度造成影响。
4字节浮点数尾数为23位,8字节浮点数尾数为52位。
float:2^23 = 8388608,一共七位,由于最左为1的一位省略了,这意味着最多能表示8位数: 2*8388608 = 16777216 。有8位有效数字,但绝对能保证的为7位,也即float的精度为7~8位有效数字;
double:2^52 = 4503599627370496,一共16位,同理,double的精度为16~17位。

总结

浮点型在计算机中,先将小数表示为2进制数,再将二进制用科学计数法来表示。
浮点型的内存结构分为3各部分,正负号,指数,尾数。
指数决定浮点型的取值范围,尾数决定浮点型的精度。
因为存在误差,不能用浮点型表示金额,金额应该用BigDecimal来表示
BigDecimal的基本用法请参见我的下一篇博客:
BigDecimal基本用法

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