`
thihy
  • 浏览: 69082 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

【讨论】为啥0.2+0.4 != 0.6(浮点数计算的精度问题)

 
阅读更多
网上有很多帖子讨论浮点数的精度问题,其中有如下命题:
  1. 0.2+0.4=0.600 000 000 000 000 1
  2. 0.58*10=5.8,但0.58*100=57.999 999 999 999 990.58*1000=580
 
首先,我们可以肯定的是:浮点数是不能完全表示实数集的(从信息论的角度很容易得出此结论),所以必然存在误差。
而对有误差的数据进行计算,会带来累加误差
 
这里讨论的都是二进制格式的浮点数表示,不包括十进制等其他进制的表示。
 

浮点数表示的误差

 

先简单介绍一下浮点数表示。大家不必自己计算,可以去http://babbage.cs.qc.cuny.edu/IEEE-754/http://babbage.cs.qc.cuny.edu/IEEE-754.old/Decimal.html。后者可以看到实际的值。
 
在IEEE标准中,浮点数用三元组 < 符号位s, 指数e, 有效数字t> 来表示 (-1)s×t×2e
 

整数的表示

 

对于十进制的整数,如果有效数字不太多,则是可以精确表示的。比如100 表示为(-1)0×1.1001×26
 

但是如果有效数字太多,则可能会出问题。比如对于12 345 678 901 234 567 000,即使使用了double(binary64)来表示,结果为1.010 101 101 010 100 101 010 011 000 110 011 101 011 000 111 110 000 1×263,但这个二进制表示其实代表的却是1.234 567 890 123 456 7e19。

 

小数0.6如何表示?

 

对于整数,二进制表示只会丢失有效数字,而不会有其他的编号。然而对于小数,则可能会很麻烦,因为小数的二进制表示可能是无限循环的。

 

比如,对于0.6,的binary64表示:

(0.6)10 = (0.100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 100 1...)2

            = (1.001 100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001...)2×2-1

 

于是,符号位s=0,指数e为-1,有效数字为1.001 100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001...

 

我们知道,binary64的有效数字最多有53位,也就是说

1.001 100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001... 的黄色部分需要被抛弃。

 

那么我们应该如何抛弃这部分数据呢?在IEEE中规定了若干舍入方法,一般来说,普遍使用的是roundTiesToEven方式来舍入。

 

roundTiesToEven方法:round到相邻的浮点数据上。如果两个浮点数据都一样近,
则round到最后一位是偶数的浮点数据上。

 

由此,0.6~=(-1)0×1.001 100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 1×2-1

 

再试试0.2和0.4

(0.2)10 =(0.001 100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 1...)2

            =(1.100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 1...)2×2-3 。

(0.4)10 =(0.011 001 100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 0...)2

            =(1.100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 10...)2×2-2

 

使用roundTiesToEven方式舍入黄色部分后,前面的部分加1。也即

 

0.2 =(1.100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 101 0)2×2-3

0.4 =(1.100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 101 0)2×2-2

 

其实,0.2和0.4的有效位数是一样的,只是指数不同。

 

到现在,我们可以发现。0.2和0.4都是向上取整的,也即浮点数表示的值比实际值是要大那么一丢丢的。

浮点数计算误差

浮点数在计算时也是有误差的。
比如对于0.2+0.4,0.2对应的指数是-3, 0.4对应的指数是-2。IEEE要求结果应该优先使用-3作为指数(也即较小的指数值)

当采用-3作为指数时,0.2和0.4需要表示成

 

0.2 =(  1.100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 101 0)2×2-3

0.4 =(11.001 100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 010   )2×2-3

 

可以看到,小数位后,0.4少了一位,我们需要用0补齐,然后计算加法。得到

 

0.2+0.4=(100.110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 100 111 0×2-3)2

 

规则化

 

0.2+0.4=(1.001 100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 001 110)2×2-1

 

我们又需要舍弃黄色部分了,在这里,由于它离两边是一样近的,于是我们round到最后一位是偶数的浮点数上,于是有

 

0.2+0.4=(1.001 100 110 011 001 100 110 011 001 100 110 011 001 100 110 011 010 0__)2×2-1

 

请注意:这里我们又往上取整了,也即此处的结果是比实际值要大那么一丢丢的。

 

而这个结果到底是多少呢?精确结果是0.600 000 000 000 000 088 817 841 970 012 523 233 890 533 447 265 625

考虑到有效数字,小数点后只保留16位,所以需要舍弃黄色部分,其结果是0.600 000 000 000 000 1=0.600 000 000 000 000 0 + 1E-16。

 

0.2+0.4的最后那个1来自哪里?

  1. 对0.2舍入时,我们偏大了一丢丢。>=1.5 × 2-57
  2. 对0.4舍入时,我们偏大了一丢丢。>=1.5 × 2-56
  3. 对0.2+0.4舍入时,我们偏大了一丢丢。= 1 × 2-54
  4. 在输出时,我们又偏大了一丢丢。
哈,我们连续三次偏大了一丢丢,也是偏大发了,就多出了哪个1来了。总共偏差>=(1.5 + 1.5 × 2 + 1 × 2×2×2) * 2-57 = 1.5625 × 2-54 >8.6e-17。考虑到在输出因为四舍五入,从而会多出最后的那个1。
 

最后的总结

  1. 浮点数表示可能存在Round
  2. 浮点数计算可能存在Round
  3. 结果输出时可能存在Round
这些Round的累加可能会引起有效数字的最后一位偏大或偏小。
题外:对于0.2或0.4,如果采用其他的Round方法,则有可能0.2+0.4=0.6的。而具体采用何种Round,是由语言实现平台决定,或者程序指定的。
8
2
分享到:
评论
10 楼 thihy 2013-05-15  
jellyfish 写道

    浮点数表示可能存在Round
    浮点数计算可能存在Round
    结果输出时可能存在Round


The error bound is Math.ulp(), assuming operations are +/-/*//. So we are safe most of the time.

However, when dealing with Math.exp(), we are in big trouble for large numbers.


哈,刚发现Math下面还有这个方法 ,3ks。
9 楼 thihy 2013-05-15  
justjavac 写道
chenjinbo1983 写道
用BigDecimal不就可以了

BigDecimal确实可以精确表示数,但是在计算数时,也无能为力。所以,还是有必要了解一下IEEE754,昨天把那个工具汉化了一下,http://justjavac.com/tools/ieee-754-floating-point-conversion-from-floating-point-to-hexadecimal.html

赞。但是原来的页面有一个不好的地方,“有效数字”的部分的Input有些短,总看不到后面的部分。要是能长一些就好了。
8 楼 justjavac 2013-05-15  
chenjinbo1983 写道
用BigDecimal不就可以了

BigDecimal确实可以精确表示数,但是在计算数时,也无能为力。所以,还是有必要了解一下IEEE754,昨天把那个工具汉化了一下,http://justjavac.com/tools/ieee-754-floating-point-conversion-from-floating-point-to-hexadecimal.html
7 楼 justjavac 2013-05-15  
thihy 写道
chenjinbo1983 写道
用BigDecimal不就可以了

实际使用时,如果对结果要求不是完整精确的,没有必要使用BigDecimal。而对于某些无理数,BigDecimal也无济于事。

这里主要是指明浮点数存在误差,所以在对浮点数进行操作时,要小心。不要潜意识地认为它是“精确的”。

对。BigDecimal不是终极解决办法,否则就没有必要使用浮点数了。BigDecimal是用定点数表示数字,所以,在范围是是有限的。
6 楼 jellyfish 2013-05-14  

    浮点数表示可能存在Round
    浮点数计算可能存在Round
    结果输出时可能存在Round


The error bound is Math.ulp(), assuming operations are +/-/*//. So we are safe most of the time.

However, when dealing with Math.exp(), we are in big trouble for large numbers.
5 楼 thihy 2013-05-14  
chenjinbo1983 写道
用BigDecimal不就可以了

实际使用时,如果对结果要求不是完整精确的,没有必要使用BigDecimal。而对于某些无理数,BigDecimal也无济于事。

这里主要是指明浮点数存在误差,所以在对浮点数进行操作时,要小心。不要潜意识地认为它是“精确的”。
4 楼 chenjinbo1983 2013-05-14  
用BigDecimal不就可以了
3 楼 thihy 2013-05-13  
justjavac 写道
提点建议,<符号位s, 指数e, 有效数字t> 这行字加粗了。

我觉得,应该把文章中有用的,或者知识点加粗。那么长的010101串,最好按4位或者8位分隔开,这样容易看。

对于大串的0101,你在后面不一样的地方加黄色背景,很赞。希望其它的不利于阅读,不利于理解的格式也调整一下。让文章锦上添花。


这格式粘贴的。感觉HTML编辑器就是不如Word爽。有时间搞定一下。
2 楼 justjavac 2013-05-13  
提点建议,<符号位s, 指数e, 有效数字t> 这行字加粗了。

我觉得,应该把文章中有用的,或者知识点加粗。那么长的010101串,最好按4位或者8位分隔开,这样容易看。

对于大串的0101,你在后面不一样的地方加黄色背景,很赞。希望其它的不利于阅读,不利于理解的格式也调整一下。让文章锦上添花。
1 楼 justjavac 2013-05-13  
赞一个,比我写的详细。

相关推荐

    为什么JavaScript中0.1 + 0.2 != 0.3

    涉及面试题:为什么 0.1 + 0.2 != 0.3?如何解决这个问题? 原因,因为 JS 采用 IEEE 754双精度版本(64位),并且只要采用 IEEE 754的语言都有该问题 我们都知道计算机是通过二进制来存储东西的,那么 0.1 在二进制...

    解决JavaScript中0.1+0.2不等于0.3问题

    console.log(0.1+0.2===0.3)// true or false??  在正常的数学逻辑思维中,0.1+0.2=0.3这个逻辑是正确的,但是在JavaScript中0.1+0.2!==0.3,这是为什么呢?这个问题也会偶尔被用来当做面试题来考查面试者对...

    关于浮点数的精度问题

    "关于浮点数的精度问题" 浮点数精度问题是一个经典的问题,对于了解和学习C语言有一定帮助。浮点数的精度问题是由于计算机对浮点数的存储方式和表示方法所致。 IEEE754 的浮点数存储格式对浮点数的表示方法进行了...

    解决javascript中的浮点数计算不精确问题

    在JavaScript编程中,浮点数计算不精确是一个常见的问题,源于其内部的二进制浮点数表示方式。本文将深入探讨这个问题,并提供解决方案。首先,我们要理解为什么会出现这种不精确性。 JavaScript遵循IEEE 754标准来...

    c语言浮点数高精度加法计算

    c语言浮点数高精度加法计算

    JS浮点数运算结果不精确的Bug解决

    最近在做项目的时候,涉及到产品价格的计算,经常会出现JS浮点数精度问题,这个问题,对于财务管理系统的开发者来说,是个非常严重的问题(涉及到钱相关的问题都是严重的问题),这里把相关的原因和问题的解决方案...

    详解JavaScript 浮点数运算的精度问题

    问题描述 在 JavaScript 中整数和浮点数都属于 Number 数据类型,所有数字都是以 64 位浮点数形式储存,即便整数也是如此。 所以我们在打印 1.00 这样的浮点数的结果是 1 而非 1.00 。...// 0.2 + 0.4 = 0.600

    浮点数精度问题

    浮点数精度问题在计算机科学和编程中是一个常见的主题,特别是在进行数学计算和数据处理时。浮点数在计算机中的表示并非像整数那样精确,这源于它们的二进制表示方式。本篇文章将深入探讨浮点数精度问题的原因、影响...

    解决JS浮点数(小数)计算加减乘除的BUG

    2. **使用库**:有些库如`decimal.js`或`big.js`专门用于处理高精度浮点数计算,它们可以避免JavaScript内置的浮点数精度问题。 3. **整数运算**:对于涉及金钱计算等需要精确结果的场景,可以考虑将数值转换为整数...

    解决JavaScript数字精度丢失问题的方法

    JS 数字精度丢失的一些典型问题 JS 数字精度丢失的原因 解决方案(一个对象+一个函数) 一、JS数字精度丢失的一些典型问题 1. 两个简单的浮点数相加 0.1 + 0.2 != 0.3 // true 这真不是 Firebug 的问题,可以用...

    浮点数(单精度浮点数,双精度浮点数)

    浮点数(单精度浮点数,双精度浮点数) 浮点数是一种数字表示方法,用于近似表示任意实数。在计算机中,浮点数由一个整数或定点数(即尾数)乘以某个基数(通常是 2)的整数次幂得到。这种表示方法类似于基数为 10 ...

    js计算精度问题解决方案

    这类库通常会提供专门的算法来避免浮点数误差,比如使用大整数库(BigInt)进行高精度计算,或者使用特定的数学操作序列来减少误差。 3. **避免直接比较**:在比较浮点数时,不要直接使用`==`或`!=`,因为它们可能...

    4字节浮点数计算工具

    本文将详细讨论“4字节浮点数计算工具”及其相关的知识点,包括浮点数的表示、16进制与10进制的转换以及在电力通信规约中的应用。 首先,浮点数是一种用于表示实数的数据类型,广泛应用于科学计算和计算机图形学等...

    浮点数精度问题解答——浮点数

    浮点数精度问题在计算机科学中是一个至关重要的概念,尤其对于进行数值计算的开发者来说,理解和掌握浮点数的表示和精度误差至关重要。...理解这些概念对于编写高精度计算代码和调试浮点异常至关重要。

    S7-200SMART_双精度浮点数转换为单精度浮点数库文件及使用说明.rar

    在处理大量数据或资源有限的设备(如S7-200SMART)时,可能需要将双精度浮点数转换为单精度浮点数来节省空间。 2. **S7-200SMART的浮点数处理**: 虽然S7-200SMART支持浮点数运算,但其硬件并不直接支持双精度...

    小数加减乘除法口算练习题(6-份).doc

    小数加减乘除法是数学中的基本运算,对于理解和应用数学至关重要,特别是在日常生活中以及IT行业的各种计算问题中。这些练习题旨在帮助学生或学习者巩固这些基础运算技巧。 在第一部分练习题中,我们可以看到一系列...

    浮点数转换器,可将浮点数、单精度 双精度的数值转换为16进制发送

    浮点数转换器,可将浮点数、单精度 双精度的数值转换为16进制发送

    对S7-200PLC双精度浮点数转单精度浮点数例程的一点补充

    双精度浮点数提供了更高的精度,但在处理能力有限的S7-200 PLC中,通常需要将其转换为单精度浮点数以便进行计算和存储。 首先,文章提供了一个双精度浮点数转换为单精度浮点数的算法。这个算法涉及到浮点数的表示,...

    浮点数计算软件.zip

    浮点数计算软件是一款专为理解和处理浮点数计算而设计的应用程序,它能够帮助用户了解4字节的二进制数值如何表示浮点数,这对于通讯传输和算法调试等场景具有很高的实用价值。在IT行业中,理解浮点数的表示和计算...

    双精度浮点数转换

    在计算机科学中,浮点数是一种用于表示数值的近似方式,主要分为单精度浮点数和双精度浮点数。这些数据类型广泛应用于各种计算,特别是在需要处理大量精确度和范围的数学运算中,例如科学计算、图像处理和游戏开发。...

Global site tag (gtag.js) - Google Analytics