往期热门文章:

1、《往期精选优秀博文都在这里了!》2、求求你!数据库不要再使用外键了?3、还在写慢SQL?4、ELK太笨重了?想放弃?快试试日志系统新贵Loki吧!5、谁再悄咪咪的吃掉异常,我上去就是一 JIO

JavaScript 作为一门诞生自上个世纪 90 年代的编程语言,从诞生之初就因为诡异的隐式类型转换等原因被黑,很多 JavaScript 的开发者还会吐槽浮点数加法的『奇葩』问题 — 为什么 0.1 + 0.2 在 JavaScript 中不等于 0.3,相信很多人都对这个问题的答案有一个大概的认识,但是都没有深入研究过,这个问题的答案让 William Kahan 在 1989 年获得图灵奖

> 0.1 + 0.20.30000000000000004

其实有上述问题的不止 JavaScript 一门编程语言,几乎所有现代的编程语言都会遇到上述问题,包括 Java、Ruby、Python、Swift 和 Go 等等,你可以在 https://0.30000000000000004.com/ 中找到常见的编程语言在计算上述表达式的结果。这不是因为它们在计算时出现了错误,而是因为浮点数计算标准的要求。

floating-point-math

图 1 - 常见的浮点数『错误』

从最开始接触 C 语言编程,作者就接触到了浮点数 float,然而在很长一段时间中,作者都将编程中的浮点数和数学中的小数看做同一个东西,不过当我们重新审视它们时,会发现这两个概念的不同之处。

  • 编程中的浮点数的精度往往都是有限的,单精度的浮点数使用 32 位表示,而双精度的浮点数使用 64 位表示;
  • 数学中的小数系统可以通过引入无限序列 ... 可以任意的实数;

在数学上我们总有办法通过额外的符号表示更复杂的数字,但是从工程的角度来看,表示无限精度的数字是不经济的,我们期望通过更小和更快的系统表示范围更大和精度更高的实数。浮点数系统是在工程上面做的权衡,IEEE 754 就是在 1985 年建立的浮点数计算标准,它定义了浮点数的算术格式、交换格式、舍入规则、操作和异常处理。讨论浮点数也无法脱离该标准,为了回答今天的问题,我们将从以下的两个角度触发:

  • 二进制无法在有限的长度中精确地表示十进制中 0.1 和 0.2;
  • 单精度浮点数、双精度浮点数的位数决定了它们能够表示的精度上限;

二进制与十进制

我们日常生活中使用的数字基本都是 10 进制的,然而计算机使用二进制的 0 和 1 表示整数和小数,所有有限的十进制整数都可以无损的转换成有限长度的二进制数字,但是要在二进制的计算机中表示十进制的小数相对就很麻烦了,我们以 0.375 为例介绍它在二进制下的表示:

小数点后面的位数依次表示十进制中的 0.5、0.25、0.125 和 0.0625 等等,这个表示方法非常好理解,每一位都是前一位的一半。0.375 在二进制表示看来确实是『整数』。然而如下图所示,想要使用二进制表示十进制中的 0.1 和 0.2 是比较复杂的:

decimals-binary-representation

图 2 - 二进制表示的十进制小数

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

dot-three-binary-representation

图 3 - 二进制表示的 0.3

这三个不同的数字都会在最后的小数部分无限循环 1100 来趋近于真实值,如果计算机中的浮点数可以表示无限循环小数就有可能解决这个问题,但是事实的真相是浮点数只会表示有限小数,所有超过特定精度的数字都会做舍入处理。

精度上限

编程语言中的浮点数一般都是 32 位的单精度浮点数 float 和 64 位的双精度浮点数 double,部分语言会使用 float32 或者 float64 区分这两种不同精度的浮点数。想要使用有限的位数表示全部的实数是不可能的,不用说无限长度的小数和无理数,因为长度的限制,有限小数在浮点数中都无法精确的表示。

float-and-double

图 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:

floating-number-example

图 5 - 0.15625 的单精度浮点数表示

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

dot-one-dot-two-floating-number

图 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],所以它们在相加后就得到的结果与我们在一开始看到的非常相似:

dot-three-floating-number

图 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 位的比特,将每一位的作用都发挥到极致,使用最紧凑和简洁的方式实现了尽可能高的精度。到最后,我们还是来看一些比较开放的相关问题,有兴趣的读者可以仔细思考一下下面的问题:

  • 有哪些编程语言内置了高精度的浮点数或者小数?
  • 如何实现一个可以精确表示所有实数(包括有理数和无理数)的系统?

图是怎么画的

本文使用的是Sketch

往期热门文章:

1、《历史文章分类导读列表!精选优秀博文都在这里了!》

2、图解Spring循环依赖,看过之后面试再也不用慌了!

3、他来了!IDEA 2020.1 新版介绍!不过升级前请注意避坑!

4、七个略火的Spring Boot+Vue开源项目!

5、Linux 11个炫酷的终端命令!你知道几个?

6、十个你可能不曾用过的Linux命令!巨好用!

7、分库分表  PK NewSQL数据库!

8、快给你的Spring Boot做个埋点监控吧!

9、一入职!就遇到上亿(MySQL)大表的优化....

10、惊呆了,Spring Boot居然这么耗内存!

valid floating point value什么意思_为什么 0.1 + 0.2 = 0.300000004?相关推荐

  1. valid floating point value什么意思_每个位置的球员,都在干些什么

    闲聊一点篮球场上位置的事. 1 有两个顶级球星在位置划分上一直很模糊--哈登和浓眉. 习惯上,我们都把哈登算得分后卫,可你也不好说哈登到底是得分后卫还是控球后卫,毕竟他控球时间比任何球员都长,ESPN ...

  2. 诊所管理软件_药一点诊所管理系统v2.0.0.1

    诊所管理软件_药一点诊所管理系统v2.0.0.1 随着计算机技术和IT业的飞速发展,门诊管理信息系统的建设经过几十年的发展取得了很大的进步.门诊管理信息化是社会和时代发展的需要,门诊能不能在激烈的竞争 ...

  3. mongoDB的读书笔记(via3.0)(00)_【概览】(02)_mongoDB3.0中的mongod启动方式小试牛刀

    mongod启动 本来想写Replica的非测试模式的集群架构的,但是实在是手痒痒,把mongoDB的3.0给download下来了,看了一两眼文档后决定还是先大概写一点点关于启动的话题,之后写Rep ...

  4. floating输入 高阻输入_上拉电阻下拉电阻高阻态

    上拉就是将不确定的信号通过一个电阻嵌位在高电平,电阻同时起限流作用.下拉同理. 上拉电阻是用来解决总线驱动能力不足时提供电流的,一般说法是拉电流.下拉电阻是用来吸收电流的,也就是我们通常所说的灌电流. ...

  5. invalid floating point operation什么意思_数据可视化有意思的小例子:Taylor Swift 歌词数据分析和可视化...

    原文地址 Data Visualization and Analysis of Taylor Swift's Song Lyrics 英语学习时间 Taylor Swift - She is the ...

  6. fifo的valid信号啥时候为高_五角枫啥时候适合移栽?五角枫移栽最佳时间

    五角枫是常见的绿化苗木,在生活中很常见,五角枫可在城市绿化中应用,也可在西部生态绿化中应用,它的叶片为掌状,秋季叶色为橙红色,整棵树都被渲染,效果很壮观.现在五角枫树苗种植的也比较多,市场上各种规格的 ...

  7. floating输入 高阻输入_按键怎么有那么多种接法,有的要电阻有的不用?实在搞糊涂了...

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 况琪 开源硬件/Arduino/物联网/交互艺术/科技出版 实名反对@李赧郎 和某匿名用户拿STM32来讲解Arduino.STM32的IO口结构跟Ard ...

  8. 实时获取ccd图像_薄膜瑕疵在线检测系统0.1mm检测精度_实时在线检测

    原标题:薄膜瑕疵在线检测系统检测精度_实时在线检测 在薄膜的实际生产过程中,由于各方面因素的影响,薄膜表面会出现诸如孔洞.蚊虫.黑点.晶点.划伤.斑点等瑕疵,严重影响了薄膜的质量,给生产商带来了不必要 ...

  9. 安装python3.8.0步骤_python3.8.0安装教程_后端开发

    如何用python画简单的动物_后端开发 用python画简单的动物的方法是:1.画笔设置,如[t.screen.screensize(canvwidth=1000,canvheight=500,bg ...

最新文章

  1. Android程序反编译
  2. 25.C++:最通俗的讲解,什么是面向过程?什么是面向对象?
  3. 分布式框架seata启动命令
  4. 贝叶斯定理、显著性检验、p值关系、分类
  5. html 字符串最后加空格,js给字符串每个字符中间加空格
  6. latex填充段落之间的留白
  7. JS 面向对象实例 prototype
  8. 外虚内实是什么意思_取名|为什么00后那么多梓涵?
  9. 一行代码解决各种IE兼容问题,IE6,IE7,IE8,IE9,IE10 (转)
  10. PHP经验——获得PHP版本信息及版本比较
  11. qq家园纵横四海的源码
  12. 知了课堂学习笔记一-Django预热-虚拟环境
  13. 发一套最完整的直升机原理(绝对完整,绝对精华)
  14. 淘客基地淘客小程序微信审核通过率90%以上的独家秘籍
  15. 电力拖动自动控制系统 华南理工大学期末重点 阮毅 长篇思维导图
  16. 国内交通银行OSA账户被注销后,企业该如何面对这种局面?
  17. hihocoder1081
  18. 摘自《大长今励志启示》
  19. JS 两数相除取百分比%并保留两位小数
  20. 中俄博览会谈下大单 云从科技“展”露头角

热门文章

  1. aws上部署hadoop_在AWS Elastic MapReduce上运行PageRank Hadoop作业
  2. primefaces_PrimeFaces 5.0 DataTable列切换器
  3. junit单元测试断言_简而言之,JUnit:单元测试断言
  4. WildFly Kubernetes exec探针
  5. java nio的演进_Java接口的防御性API演进
  6. 注意Java 8的[Pri​​mitive] Stream.iterate()中的递归
  7. javaone_JavaOne 2012 – 2400小时! 一些建议
  8. jvm gc策略_IBM JVM调整– gencon GC策略
  9. JavaOne和OOW 2015总结
  10. vaadin_Vaadin附加组件和Maven