热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

input0.1无法相加_为什么0.1+0.2=0.300000004

为什么这么设计(Why’sTHEDesign)是一系列关于计算机领域中程序设计决策的文章,我们在这个系列的每一篇文章中都会提出一个具体的问

为什么这么设计(Why’s THE Design)是一系列关于计算机领域中程序设计决策的文章,我们在这个系列的每一篇文章中都会提出一个具体的问题并从不同的角度讨论这种设计的优缺点、对具体实现造成的影响。

Javascript 作为一门诞生自上个世纪 90 年代的编程语言[^1],从诞生之初就因为诡异的隐式类型转换等原因被黑,很多 Javascript 的开发者还会吐槽浮点数加法的『奇葩』问题 — 为什么 0.1 + 0.2 在 Javascript 中不等于 0.3,相信很多人都对这个问题的答案有一个大概的认识,但是都没有深入研究过,这个问题的答案让 William Kahan 在 1989 年获得图灵奖[^2]。
> 0.1 + 0.2
0.30000000000000004
其实有上述问题的不止 Javascript 一门编程语言,几乎所有现代的编程语言都会遇到上述问题,包括 Java、Ruby、Python、Swift 和 Go 等等,你可以在 https://0.30000000000000004.com/ 中找到常见的编程语言在计算上述表达式的结果[^3]。这不是因为它们在计算时出现了错误,而是因为浮点数计算标准的要求。

d56bacd55c2157513a91924d2e91a691.png

图 1 - 常见的浮点数『错误』
从最开始接触 C 语言编程,作者就接触到了浮点数 float,然而在很长一段时间中,作者都将编程中的浮点数和数学中的小数看做同一个东西,不过当我们重新审视它们时,会发现这两个概念的不同之处。
编程中的浮点数的精度往往都是有限的,单精度的浮点数使用 32 位表示,而双精度的浮点数使用 64 位表示;
数学中的小数系统可以通过引入无限序列 ... 可以任意的实数[^4];
在数学上我们总有办法通过额外的符号表示更复杂的数字,但是从工程的角度来看,表示无限精度的数字是不经济的,我们期望通过更小和更快的系统表示范围更大和精度更高的实数。浮点数系统是在工程上面做的权衡,IEEE 754 就是在 1985 年建立的浮点数计算标准,它定义了浮点数的算术格式、交换格式、舍入规则、操作和异常处理[^5]。讨论浮点数也无法脱离该标准,为了回答今天的问题,我们将从以下的两个角度触发:
[^5]: 754-2019 - IEEE Standard for Floating-Point Arithmetic https://standards.ieee.org/content/ieee-standards/en/standard/754-2019.html
二进制无法在有限的长度中精确地表示十进制中 0.1 和 0.2;
单精度浮点数、双精度浮点数的位数决定了它们能够表示的精度上限;
二进制与十进制
我们日常生活中使用的数字基本都是 10 进制的,然而计算机使用二进制的 0 和 1 表示整数和小数,所有有限的十进制整数都可以无损的转换成有限长度的二进制数字,但是要在二进制的计算机中表示十进制的小数相对就很麻烦了,我们以 0.375 为例介绍它在二进制下的表示[^6]:
[^6]: Decimal to Binary converter https://www.rapidtables.com/convert/number/decimal-to-binary.html
小数点后面的位数依次表示十进制中的 0.5、0.25、0.125 和 0.0625 等等,这个表示方法非常好理解,每一位都是前一位的一半。0.375 在二进制表示看来确实是『整数』。然而如下图所示,想要使用二进制表示十进制中的 0.1 和 0.2 是比较复杂的:

c12e16c6cf257f1dc4f81a0dfc4db8bd.png

图 2 - 二进制表示的十进制小数
无论是 0.1 还是 0.2,这两个数字都不是二进制中的『整数』,我们没有办法精确地表示它们,只能通过无限循环小数尝试接近它们的真实值;与之相似的是,它们相加的结果 0.3 也无法用有限长度的二进制表示:

f6992e0228c3a5a5229d9502c17d63e2.png

图 3 - 二进制表示的 0.3
这三个不同的数字都会在最后的小数部分无限循环 1100 来趋近于真实值,如果计算机中的浮点数可以表示无限循环小数就有可能解决这个问题,但是事实的真相是浮点数只会表示有限小数,所有超过特定精度的数字都会做舍入处理。
精度上限
编程语言中的浮点数一般都是 32 位的单精度浮点数 float 和 64 位的双精度浮点数 double,部分语言会使用 float32 或者 float64 区分这两种不同精度的浮点数。想要使用有限的位数表示全部的实数是不可能的,不用说无限长度的小数和无理数,因为长度的限制,有限小数在浮点数中都无法精确的表示。

476209c327f46f5dc8de77aad5787de9.png

图 4 - 单精度与双精度浮点数
单精度浮点数 float 总共包含 32 位,其中 1 位表示符号、8 位表示指数,最后 23 位表示小数;
双精度浮点数 double 总共包含 64 位,其中 1 位表示符号,11 位表示指数,最后 52 位表示小数;
我们以单精度浮点数 0.15625 为例,介绍该浮点数在计算机二进制中的表示方法,如下图所示,符号位 0 表示该浮点数为正数,中间的 8 位指数总共可以表示 256 个数字,其中从 [0, 126] 表示 [-127, -1],而 [127, 255] 表示 [0, 128],二进制的 01111100 是十进制的 124,表示 ,最后的 23 位是二进制的小数 0.25:

f50654e33c08767bdc128ec5fed62639.png

图 5 - 0.15625 的单精度浮点数表示
通过上图中的公式 可以将浮点数的二进制表示转换成十进制的小数。0.15625 虽然还可以用单精度的浮点数精确表示,但是 0.1 和 0.2 只能使用浮点数表示近似的值:

394eda55a72ebff37d2b0b0ad08f5cfc.png

图 6 - 0.1 和 0.2 的单精度浮点数表示
因为 0.2 和 0.1 只是指数稍有不同,所以上图中只展示了 0.1 对应的单精度浮点数,从上图的结果我们可以看出,0.1 和 0.2 在浮点数中只能用近似值来代替,精度十分有限,因为单精度浮点数的小数位为 23,双精度的小数位为 52,同时都隐式地包含首位的 1,所以它们的精度在十进制中分别是 和 位。
因为 0.1 和 0.2 使用单精度浮点数表示的实际值为 0.100000001490116119384765625 和 0.20000000298023223876953125[^7],所以它们在相加后就得到的结果与我们在一开始看到的非常相似:

a6ebed0d688ad970377afe3d9f15bc4c.png

图 7 - 0.1 加 0.2 的结果
上图只是使用单精度浮点数表示的数字,如果使用双精度浮点数,最终结果中的 3 和 4 之间会有更多的 0,但是小数出现的顺序是非常相似的。浮点数的运算法则相对来说比较复杂,感兴趣的读者可以自行搜索相关的资料,我们在这里不展开介绍了。
总结
当我们在不同编程语言中看到 0.300000004 或者 0.30000000000000004 时不应该感到惊讶,这其实说明编程语言正确实现了 IEEE 754 标准中描述的浮点数系统,在使用单精度和双精度浮点数时也应该牢记它们只有 7 位和 15 位的有效位数。
在交易系统或者科学计算的场景中,如果需要更高的精度小数,可以使用具有 28 个有效位数的 decimal 或者直接使用分数,不过这些表示方法的开销也随着有效位数的增加而提高,我们应该按照需要选择最合适的方法。重新回到今天的问题 — 0.1 和 0.2 相加不等于 0.3 的原因包括以下两个:
使用二进制表达十进制的小数时,某些数字无法被有限位的二进制小数表示;
单精度和双精度的浮点数只包括 7 位或者 15 位的有效小数位,存储需要无限位表示的小数时只能存储近似值;
浮点数系统的设计是一个比较有趣的工程问题,因为操作系统一般都是 32 位或者 64 位的,浮点数充分利用了 32/64 位的比特,将每一位的作用都发挥到极致,使用最紧凑和简洁的方式实现了尽可能高的精度。到最后,我们还是来看一些比较开放的相关问题,有兴趣的读者可以仔细思考一下下面的问题:
有哪些编程语言内置了高精度的浮点数或者小数?
如何实现一个可以精确表示所有实数(包括有理数和无理数)的系统?



推荐阅读
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 设计模式实现对象的复用——享元模式的概述和角色解析
    本文介绍了设计模式中的享元模式,该模式通过共享技术实现了大量细粒度对象的复用,从而提高系统性能。文章详细解析了享元模式的概述和角色,包括抽象享元类、具体享元类和非共享享元类。通过本文的阅读,读者可以了解享元模式的原理和应用场景,从而在实际开发中更好地利用该模式实现对象的复用。 ... [详细]
  • 恶意软件分析的最佳编程语言及其应用
    本文介绍了学习恶意软件分析和逆向工程领域时最适合的编程语言,并重点讨论了Python的优点。Python是一种解释型、多用途的语言,具有可读性高、可快速开发、易于学习的特点。作者分享了在本地恶意软件分析中使用Python的经验,包括快速复制恶意软件组件以更好地理解其工作。此外,作者还提到了Python的跨平台优势,使得在不同操作系统上运行代码变得更加方便。 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • Python实现变声器功能(萝莉音御姐音)的方法及步骤
    本文介绍了使用Python实现变声器功能(萝莉音御姐音)的方法及步骤。首先登录百度AL开发平台,选择语音合成,创建应用并填写应用信息,获取Appid、API Key和Secret Key。然后安装pythonsdk,可以通过pip install baidu-aip或python setup.py install进行安装。最后,书写代码实现变声器功能,使用AipSpeech库进行语音合成,可以设置音量等参数。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 20211101CleverTap参与度和分析工具功能平台学习/实践
    1.应用场景主要用于学习CleverTap的使用,该平台主要用于客户保留与参与平台.为客户提供价值.这里接触到的原因,是目前公司用到该平台的服务~2.学习操作 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 本文介绍了OpenStack的逻辑概念以及其构成简介,包括了软件开源项目、基础设施资源管理平台、三大核心组件等内容。同时还介绍了Horizon(UI模块)等相关信息。 ... [详细]
  • 本文介绍了绕过WAF的XSS检测机制的方法,包括确定payload结构、测试和混淆。同时提出了一种构建XSS payload的方法,该payload与安全机制使用的正则表达式不匹配。通过清理用户输入、转义输出、使用文档对象模型(DOM)接收器和源、实施适当的跨域资源共享(CORS)策略和其他安全策略,可以有效阻止XSS漏洞。但是,WAF或自定义过滤器仍然被广泛使用来增加安全性。本文的方法可以绕过这种安全机制,构建与正则表达式不匹配的XSS payload。 ... [详细]
  • Iamtryingtocreateanarrayofstructinstanceslikethis:我试图创建一个这样的struct实例数组:letinstallers: ... [详细]
author-avatar
hazouri林_978
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有