如何解决0.1 +0.2===0.30000000000000004类问题

在这里插入图片描述

上篇博客深度剖析了0.1+0.2 === 0.30000000000000004的原因。
这篇博客将主要提供几种解决小数精度丢失问题的Javascript类库的代码示例,以及简单的原生EcmaScript方法的代码示例。

一.类库部分

math.js

math.js是JavaScript和Node.js的一个广泛的数学库。支持数字,大数,复数,分数,单位和矩阵等数据类型的运算。

官网:http://mathjs.org/
GitHub:https://github.com/josdejong/mathjs

0.1+0.2 ===0.3实现代码:

var math = require('mathjs')
console.log(math.add(0.1,0.2))//0.30000000000000004
console.log(math.format((math.add(math.bignumber(0.1),math.bignumber(0.2)))))//'0.3'
decimal.js

为 JavaScript 提供十进制类型的任意精度数值。

官网:http://mikemcl.github.io/decimal.js/

GitHub:https://github.com/MikeMcl/decimal.js

var Decimal = require('decimal.js')
x = new  Decimal(0.1)
y = 0.2
console.log(x.plus(y).toString())//'0.3'
bignumber.js

用于任意精度算术的JavaScript库。

官网:http://mikemcl.github.io/bignumber.js/

Github:https://github.com/MikeMcl/bignumber.js

var BigNumber = require("bignumber.js")
x = new BigNumber(0.1)
y = 0.2
console.log(x.plus(y).toString())//'0.3'
big.js

用于任意精度十进制算术的小型快速JavaScript库。
官网:http://mikemcl.github.io/big.js/
Github:https://github.com/MikeMcl/big.js/

var Big = require("big.js")
x = new Big(0.1)
y = 0.2
console.log(x.plus(y).toString())//'0.3'

有一个需要注意的点,使用类库此时输出的0.3是String类型,因此若想保持为Number类型,可使用parseFloat()方法。

还有一个注意点,在本地install测试的时候,npm i mathjs -g ,require是也要require(‘mathjs’),而不是带点的math.js,因为josdejong这哥们在创建项目的时候就命名为mathjs,而同时拥有上述decimal.js, bignumber.js和big.js的MikeMcl,项目名字就带了dot,因此安装和引入时,都是xxx.js的形式。

如何在这三个类库之间做选择,还需要大家自己根据具体情况具体分析,我在这里就不赘述了。

最后,教大家一个线上直接测试的网站,https://npm.runkit.com,子路径输入想要测试的Node.js package名,就可以实现在线测试包中的api了。
例如:
math.js:https://npm.runkit.com/mathjs
big.js:https://npm.runkit.com/big.js

二、原生方法

类库其实很强大,我们计算0.1+0.2其实只是用到了冰山一角,那么我们如何使用原生的EcmaScript代码来应用于简单的问题场景呢?

这就要用到Number.prototype.toFixed()这个方法了。

浮点数运算

toFixed() 方法

浮点数运算的解决方案有很多,这里给出一种目前常用的解决方案, 在判断浮点数运算结果前对计算结果进行精度缩小,因为在精度缩小的过程总会自动四舍五入。

toFixed() 方法使用定点表示法来格式化一个数,会对结果进行四舍五入。语法为:

JavaScript 代码:
numObj.toFixed(digits)
参数 digits 表示小数点后数字的个数;介于 0 到 20 (包括)之间,实现环境可能支持更大范围。如果忽略该参数,则默认为 0。

返回一个数值的字符串表现形式,不使用指数记数法,而是在小数点后有 digits 位数字。该数值在必要时进行四舍五入,另外在必要时会用 0 来填充小数部分,以便小数部分有指定的位数。 如果数值大于 1e+21,该方法会简单调用 Number.prototype.toString()并返回一个指数记数法格式的字符串。

特别注意:toFixed() 返回一个数值的字符串表现形式。

具体可以查看 MDN中的说明,那么我们可以这样解决精度问题:

JavaScript 代码:

parseFloat((数学表达式).toFixed(digits)); // toFixed() 精度参数须在 0 与20 之间,建议传2。2是为了处理5/9这样的无限循环小数。
// 运行
parseFloat((0.1 + 0.2).toFixed(10))//结果为0.3
parseFloat((0.3 / 0.1).toFixed(10)) // 结果为 3  
parseFloat((0.7 * 180).toFixed(10))//结果为126
parseFloat((1.0 - 0.9).toFixed(10)) // 结果为 0.1   
parseFloat((9.7 * 100).toFixed(10)) // 结果为 970 
parseFloat((2.22 + 0.1).toFixed(10)) // 结果为 2.32

在Browser环境精度参数允许0~100位之间(包括100),测试版本为Chrome62(64位)和Firefox56 (32 位)。
在Nodejs环境中,只能是0~20之间,测试版本为v6.9.5。

其次就是toFixed()的浏览器兼容性讨论,MDN给出的结果全部是YES,无论desktop端还是mobile端,也就是说不用担心toFixed()的兼容性问题(ie8- 我们不做讨论)。

desktop端:

mobile端:

Thanks:
http://www.css88.com/archives/7340#more-7340
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed


2019.7.28更新:

parseFloat((5/9).toFixed(10))返回0.5555555556。所以更为建议的做法是parseFloat((5/9).toFixed(2)),把小数控制在2位。

钱的计算是最复杂的,例如人民币的计算,其实最终精确到分就很可以了,元角。厘这个单位很小,生活中已经很少见了。

期待和大家交流,共同进步,欢迎大家加入我创建的与前端开发密切相关的技术讨论小组:

努力成为优秀前端工程师!

<think>嗯,用户问为什么0.10.2等于0.30000000000000004。这个问题看起来和浮点数精度有关,但得仔细想想怎么解释清楚。首先,我应该回忆一下计算机中浮点数的表示方法,比如IEEE 754标准。 对,计算机是用二进制来存储小数的,但像0.10.2这样的十进制数在二进制里其实是无限循环的。比如0.1转换成二进制是0.0001100110011...,不断循环,对吧?同样,0.2也是似的情况,只是小数点后多一位,所以二进制是0.001100110011...。这样的话,当计算机用双精度浮点数(64位)存储它们的时候,只能保留一定的有效位数,后面的部分会被截断,导致精度丢失。 接下来,当这两个近似值相加的时候,误差会累积。相加后的结果可能无法精确表示为另一个浮点数,所以又会有一次近似,结果就变成了0.30000000000000004。这时候要说明转换过程的具体步骤,可能得用实际例子,比如把0.10.2的二进制形式相加后的结果转换成十进制,看是不是得到这个数值。 不过用户可能不太了解二进制转换,所以得用简单的方式解释。可能需要举例子,比如似十进制的1/3无法精确表示,只能近似为0.3333,多个这样的数相加时误差就会显现出来。然后联系到二进制的情况,同样的问题,但因为是计算机内部处理,所以结果看起来很奇怪。 还要提到这是符合IEEE 754标准的,所有使用该标准的编程语言都会有这个问题,比如JavaScript、Python等。说明这不是语言本身的错误,而是浮点数设计的局限性。同时,可以建议解决方法,比如使用整数运算,或者高精度库来处理小数运算,避免精度问题。 另外,可能需要检查一下具体的数值转换过程,确保例子准确。比如0.10.2的双精度表示,以及它们的和。可能需要查证一下具体的二进制位,但为了简化,可以不用深入太多细节,重点放在误差的累积上。 最后总结一下,浮点数在计算机中的表示限制导致无法精确存储某些十进制小数,运算时的近似值累积造成微小的误差,从而出现0.1+0.2不等于0.3的情况。这是计算机科学中常见的问题,需要注意在处理金融等需要高精度的计算时使用适当的方法。</think>### 为什么 $0.1 + 0.2 = 0.30000000000000004$? 这个问题的根源在于计算机使用**二进制浮点数**表示小数,而某些十进制小数无法精确转换为二进制形式。以下是逐步解释: --- #### 1. **十进制小数在二进制中的表示问题** - **十进制转二进制**: 例如,$0.1$ 的二进制表示是 $0.0001100110011\ldots$(无限循环),$0.2$ 是 $0.001100110011\ldots$(同样是无限循环)。 计算机存储时只能保留有限位数(双精度浮点数用**52位尾数**),因此会**截断**并产生微小误差。 --- #### 2. **浮点数的存储方式(IEEE 754标准)** - 双精度浮点数(64位)的格式为: $$(-1)^{\text{sign}} \times 2^{\text{exponent}} \times (1 + \text{fraction})$$ - **尾数**(52位)存储小数部分,无法完整表示无限循环的二进制数。 - **实际存储值**: - $0.1$ 的实际存储值约为:$0.10000000000000000555\ldots$ - $0.2$ 的实际存储值约为:$0.20000000000000001110\ldots$ --- #### 3. **误差的累积** - **加法过程**: $$0.1 + 0.2 \approx 0.10000000000000000555 + 0.20000000000000001110 = 0.30000000000000004441$$ 结果需要再次被舍入到最近的浮点数,最终得到: $$0.30000000000000004$$ --- #### 4. **为什么不是 $0.3$?** - $0.3$ 的二进制表示也是无限循环的: $0.010011001100110011\ldots$ 计算机无法精确存储它,实际存储值为:$0.29999999999999998889\ldots$ 因此,$0.3$ 和 $0.1+0.2$ 的存储结果不同。 --- #### 5. **验证方法** - **JavaScript 示例**: ```javascript console.log(0.1 + 0.2); // 输出 0.30000000000000004 console.log(0.1 + 0.2 === 0.3); // 输出 false ``` --- #### 6. **如何避免问题?** - **使用整数运算**: 例如,将金额转换为分(以整数计算)。 - **高精度库**: 在 Python 中可使用 `decimal` 模块,或在 JavaScript 中使用 `big.js`。 - **比较时设置误差范围**: ```javascript Math.abs(0.1 + 0.2 - 0.3) < 1e-10; // true ``` --- ### 总结 - **核心原因**:二进制浮点数无法精确表示某些十进制小数,导致运算误差累积。 - **通用性**:这是所有遵循 IEEE 754 标准的编程语言的共同问题(如 Python、JavaScript、Java)。 - **解决方法**:根据场景选择整数运算、高精度库或容忍微小误差。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

趁你还年轻233

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值