0.30000000000000004

简介: 0.30000000000000004问题是计算机科学领域的经典BUG, 由比尔盖茨那一代人标准化的浮点数表示法造福了一代人也祸害了一代人, 由此引出了不少的坑, 比如大多数编程语言中0.


        0.30000000000000004问题是计算机科学领域的经典BUG, 由比尔盖茨那一代人标准化的浮点数表示法造福了一代人也祸害了一代人, 由此引出了不少的坑, 比如大多数编程语言中0.1+0.2==0.30000000000000004.遇到这个问题不要担心, 你的编译环境没有坏, 只是计算机在做进制转换的时候需要绕一些丸子, 本文来具体分析一下这个bug背后的秘密, 也可以访问它的官解: http://0.30000000000000004.com/


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy预览



    众所周知JS仅有Number这个数值类型,而Number采用的时IEEE 754 64位双精度浮点数编码。而浮点数表示方式具有以下特点:

  1. 浮点数可表示的值范围比同等位数的整数表示方式的值范围要大得多;

  2. 浮点数无法精确表示其值范围内的所有数值,而有符号和无符号整数则是精确表示其值范围内的每个数值;

  3. 浮点数只能精确表示m*2e的数值;

  4. 当biased-exponent为2e-1-1时,浮点数能精确表示该范围内的各整数值;

  5. 当biased-exponent不为2e-1-1时,浮点数不能精确表示该范围内的各整数值。

  由于部分数值无法精确表示(存储),于是在运算统计后偏差会愈见明显。

 


    不仅是JavaScript会产生这种问题,只要是采用IEEE 754 Floating-point的浮点数编码方式来表示浮点数时,则会产生这类问题。绝对完美的数值编码方案是不存在的,为了处理方便,这个标准引入了大量的折衷和妥协,建立在这种表达方式上的算法(例如除法运算)也一样。由于数值表达方式存在“缺陷”,运算结果不可避免地堆聚起越来越多的误差, 补丁越来越多, 浮点算法就成了和路由协议一样的筛子。下面我们来分析整个运算过程。

  1. 0.1 的二进制表示为 1.1001100110011001100110011001100110011001100110011001 1(0011)+ * 2^-4;

  2. 当64bit的存储空间无法存储完整的无限循环小数,而IEEE 754 Floating-point采用round to nearest, tie to even的舍入模式,因此0.1实际存储时的位模式是0-01111111011-1001100110011001100110011001100110011001100110011010;

  3. 0.2 的二进制表示为 1.1001100110011001100110011001100110011001100110011001 1(0011)+ * 2^-3;

  4. 当64bit的存储空间无法存储完整的无限循环小数,而IEEE 754 Floating-point采用round to nearest, tie to even的舍入模式,因此0.2实际存储时的位模式是0-01111111100-1001100110011001100110011001100110011001100110011010;

  5. 实际存储的位模式作为操作数进行浮点数加法,得到 0-01111111101-0011001100110011001100110011001100110011001100110100。转换为十进制即为0.30000000000000004。



扩展一下, 就有了多种类似的产生式:


Why 0.7 * 180===125.99999999998?                 

  1. 0.7实际存储时的位模式是0-01111111110-0110011001100110011001100110011001100110011001100110;

  2. 180实际存储时的位模式是0-10000000110-0110100000000000000000000000000000000000000000000000;

  3. 实际存储的位模式作为操作数进行浮点数乘法,得到0-10000000101-1111011111111111111111111111111111111111101010000001。转换为十进制即为125.99999999998。

 

Why 1000000000000000128 === 1000000000000000129 ?

  1. 1000000000000000128实际存储时的位模式是0-10000111010-1011110000010110110101100111010011101100100000000001;

  2. 1000000000000000129实际存储时的位模式是0-10000111010-1011110000010110110101100111010011101100100000000001;

  3. 因此1000000000000000128和1000000000000000129的实际存储的位模式是一样的。






日常开发规范


■ 大多数Web页面不需要小数
避免使用小数,尽量设法使用整数。确保数组的索引都是整数。按分(而不是元)计算金额。百分比放大100倍计算以避免出现小数。尽可能不用除法(/)和模(%)运算,因为大多数情况下它们直接导致出现浮点数。如果必须使用除法,立即用Math.round方法回归整数运算。

■ 如果必须使用浮点数,则尽可能引入冗余小数位——即在程序要求的运算精度之外,再增加小数位
如果程序需要5位数字的小数精度,则在运算中至少保留6位的小数,8位更好。冗余位越多,累计误差的影响越小。

■ 避免在同一个表达式中使用相差太大或太小的数值
对两个非常接近的数值执行减法或比较操作很容易出错。将很小的数值和很大数值相加无异于浪费时间,小的数值很可能被当作0。不过,很小的数值乘以很大的数值一般不会出现问题,例如2 * 12345678会得到正确的结果24691356。但是,0.1 - 0.09的结果是0.010000000000000009。

■ 用isFinite()和isNaN()检查运算结果
通过表单提交任何数值运算结果之前,一定要先检查数据的合法性。

■ 慎用数值运算
程序涉及的数值运算越少,引入误差的可能就越小。视浮点数为贵客,不可任意驱使。

目录
相关文章
|
Web App开发 Android开发 开发者
|
Web App开发 移动开发 JavaScript
|
2天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
28天前
|
运维 Cloud Native Devops
一线实战:运维人少,我们从 0 到 1 实践 DevOps 和云原生
上海经证科技有限公司为有效推进软件项目管理和开发工作,选择了阿里云云效作为 DevOps 解决方案。通过云效,实现了从 0 开始,到现在近百个微服务、数百条流水线与应用交付的全面覆盖,有效支撑了敏捷开发流程。
19258 29
|
29天前
|
人工智能 自然语言处理 搜索推荐
阿里云Elasticsearch AI搜索实践
本文介绍了阿里云 Elasticsearch 在AI 搜索方面的技术实践与探索。
18798 20
|
28天前
|
Rust Apache 对象存储
Apache Paimon V0.9最新进展
Apache Paimon V0.9 版本即将发布,此版本带来了多项新特性并解决了关键挑战。Paimon自2022年从Flink社区诞生以来迅速成长,已成为Apache顶级项目,并广泛应用于阿里集团内外的多家企业。
17505 13
Apache Paimon V0.9最新进展
|
30天前
|
存储 人工智能 前端开发
AI 网关零代码解决 AI 幻觉问题
本文主要介绍了 AI Agent 的背景,概念,探讨了 AI Agent 网关插件的使用方法,效果以及实现原理。
18693 15
|
28天前
|
人工智能 自然语言处理 搜索推荐
评测:AI客服接入钉钉与微信的对比分析
【8月更文第22天】随着人工智能技术的发展,越来越多的企业开始尝试将AI客服集成到自己的业务流程中。本文将基于《10分钟构建AI客服并应用到网站、钉钉或微信中》的解决方案,详细评测AI客服在钉钉和微信中的接入流程及实际应用效果,并结合个人体验分享一些心得。
9907 9