php 把0放左边1放右边,揭秘 0.1 + 0.2 != 0.3(php 請自覺點用round)

本文详细解析了IEEE754浮点数标准在JavaScript等语言中导致0.1+0.2不等于0.3的现象。通过介绍浮点数的二进制表示法,特别是小数部分的乘二取整运算,揭示了精度丢失的原因。并提供了手动计算和使用double-bits库验证的示例,帮助读者深入理解这个问题。
摘要由CSDN通过智能技术生成

事實上,不僅僅是 JS,在其他采用 IEEE754 浮點數標准的語言中,0.1 + 0.2 都不會等於 0.3,但是 0.2 + 0.3 卻等於 0.5,這是為何?想必這類問題也困擾着不少程序員。

IEEE754 浮點數的演算

我們知道,科學計數法中 30000 可以寫成 3x104,以 10 為底數 4 為指數的科學計數法。在 IEEE754 標准中是比較類似的,只不過它是二進制數,底數也為 2。

IEEE 754 中最常用的浮點數值表示法是:單精確度(32位)和雙精確度(64位),JavaScript 采用的是后者。舉個例子,十進制數 150,使用雙精度浮點數表示法,表示如下:

// D 表示十進制,B 表示二進制150D = 2^8 * 0.1001011B // 后面省略了 46 個 0

可以通過短除法計算:

150 余數位÷ 2---------------75 0÷ 2---------------37 1÷ 2---------------18 1÷ 2---------------9 0÷ 2---------------4 1÷ 2---------------2 0÷ 2---------------1 0÷ 2---------------0 1

最后一個余數為高位值,於是拿到 150 對應的二進制數位 1001011,也就等於 2^8 * 0.1001011。

上面是整數的表示法,而小數的表示法采用的是乘二取整,如 0.1,它的二進制表示為:

// (0011) 表示循環0.1D = 2^-3 * 0.110011(0011)

其演算方法如下:

0.1 整數位× 2---------------0.2 0× 2---------------0.4 0 * ↓× 2---------------0.8 0× 2---------------1.6 1× 2---------------1.2 1× 2---------------0.4 0 * ↑(0011循環)

與整數不同的是,第一個計算得到的整數位為最高位,故 0.1 對應的二進制數為 0.000110011(0011),也就等於 2^-3 0.1100110011(0011)。

如果一個數既包含整數部分,又包含小數部分,其表示法的計算,需要分拆為整數和小數兩部分,然后相加得到結果。

IEEE754 浮點數精度丟失

IEEE754 浮點數表示法的數據格式如下圖:

// 下圖采用大端表示,高位在左,低位在右。sign exponent fraction+---+----------+---------------------+| 1 | 2~12 | 13~64 |+---+----------+---------------------+

符號位:高位第 1 位,如圖 sign 部分

指數位:高位第 2~12 位,如圖 exponent 部分

尾數位:剩下的 fraction 部分

從上面小數的乘二取整演算中可以看到,有些小數對應的二進制數是無法寫全的,比如 0.1,而 fraction 尾數部分有要求,只允許 52 位,超過部分進一舍零。

那么,我們就可以得到:

0.1D= 2^-4 * 1.10011(0011)B= 2^-4 * 1.10011(0011 repeat 12 times)0011B // ← 最后一位為 1,進 1= 2^-4 * 1.10011(0011 repeat 12 times)010B

揭秘 0.1 + 0.2

根據上面我們了解到的知識,我們可以很容易算出這些值:

0.1D = 2^-4 * 1.1001100110011001100110011001100110011001100110011010B0.2D = 2^-3 * 1.1001100110011001100110011001100110011001100110011010B0.3D = 2^-2 * 1.0011001100110011001100110011001100110011001100110011B

0.1 + 0.2 時,先將兩者指數統一為 -3,故 0.1 小數點向左移一位,於是:

0.1100110011001100110011001100110011001100110011001101B+ 1.1001100110011001100110011001100110011001100110011010B------------------------------------------------------------= 10.0110011001100110011001100110011001100110011001100111B

得到的二進制數為:

10.0110011001100110011001100110011001100110011001100111B

小數點往左移一位使得整數部分為 1,此時尾數部分為 53 位,進一舍零,於是得到最后的值是:

2^-2 * 1.0011001100110011001100110011001100110011001100110100

這個值轉化成真值,結果為:0.30000000000000004。那么 0.1 + 0.2 = 0.30000000000000004 的推演到這里就結束了。

相關驗證

畢竟咱們手動計算可能存在筆誤,可以通過一個叫做 double-bits 的 npm 進行推演,我寫了一個小 demo,感興趣的可以玩耍下:

const db = require('double-bits');const pad = require('pad');// [lo, hi] where lo is a 32 bit integer and hi is a 20 bit integer.const base2Str = (n) => {const f = db.fraction(n);const s = db.sign(n) ? '-' : '';const e = `2^${db.exponent(n) + 1}`;const t = `0.${pad(f[1].toString(2), 20, '0')}${pad(f[0].toString(2), 32, '0')}`;return `${s}${e} * ${t}`;};console.log(base2Str(0.1).toString(2));console.log(base2Str(0.2).toString(2));console.log(base2Str(0.3).toString(2));console.log(base2Str(1.2).toString(2));

上面輸出結果為:

2^-3 * 0.110011001100110011001100110011001100110011001100110102^-2 * 0.110011001100110011001100110011001100110011001100110102^-1 * 0.100110011001100110011110011001100110011001100110011002^1 * 0.10011001100110011001111001100110011001100110011001100

最后

為了按照計算機的思維,IEEE754 的標准來計算 0.1 + 0.2,又重新復習了一遍大學計算機基礎的知識,原碼、反碼、補碼,以及除二取余、乘二取整計算法,最后能夠推演出來,也算是一個勝利吧~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值