Rust番外——0.1+0.2為什么不等于0.3 (上篇)

Rust番外——0.1+0.2為什么不等于0.3 (下篇)

0x00 開篇

上一篇文章主要介紹了一些基本概念,既然已經(jīng)了解了基本概念了,那么這篇文章也就很容易理解了。

0x02 無限浮點數(shù)

在十進制中存在無限小數(shù),π 是一個無限不循環(huán)小數(shù),還有 1 ÷ 3 = 0.333333...這是一個無限循環(huán)小數(shù) 。那其實在二進制中也存在同樣的問題。例如:十進制的0.1,按照上一篇文章所說的,轉化為二進制過程如下:

0.1 => 0.1 × 2 = 0.2
0.2 => 0.2 × 2 = 0.4
0.4 => 0.4 × 2 = 0.8
0.8 => 0.8 × 2 = 1.6
1.6 => 0.6 × 2 = 1.2
0.2 => 0.2 × 2 = 0.4
0.4 => 0.4 × 2 = 0.8
......此處省略

那最終結果就是0.00011001100..... 由此可見十進制的有限小數(shù)0.1轉化為二進制后會變成無限小數(shù),精度會丟失。同理十進制0.2表示為二進制則是0.00110011001.....。

0x03 轉換為指數(shù)

普通轉換

其實文章讀到這里,大家也應該有點兒理解是什么原因了。以64位浮點數(shù)運算為例。

十進制0.1的二進制指數(shù):

1.1001 1001 1001 1001 1001 1001 1001 10001 1001
1001 1001 1001 1001... × 2 ^ -100

十進制0.2的二進制指數(shù):

1.1001 1001 1001 1001 1001 1001 1001 10001 1001
1001 1001 1001 1001... × 2 ^ -11
按照IEEE754標準轉換

上篇文章提到IEEE754標準,只有52位數(shù),如果是一個無限小數(shù),則需按照“四舍五入”標準舍棄。

十進制0.1的二進制指數(shù)(IEEE754):

1.1001 1001 1001 1001 1001 1001 1001 10001 1001
1001 1001 1001 1010 (第53位是1,舍棄時需要進位) × 2 ^ -100

十進制0.2的二進制指數(shù)(IEEE754):

1.1001 1001 1001 1001 1001 1001 1001 10001 1001
1001 1001 1001 1010 (第53位是1,舍棄時需要進位) × 2 ^ -11

為了看得更加清楚,可以看下面的圖片。

image-20220118190452395

0x04 計算

由于指數(shù)位不同,所有不能直接進行比較。首先就是要移位,我們對0.1進行移位。

image-20220118193725057

然后在求和計算得出結果。

image-20220118200844927

最終得出0.3的二進制為

0.010 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0111

將上面的結果轉化為十進制:

二進制轉為十進制要從右到左用二進制的每個數(shù)去乘以2的相應次方,小數(shù)點后則是從左往右。

= 0 × 2^-1 + 1 × 2^-2 + ... + 1 × 2^-52
= 0.30000000000000004 ?

0x05 驗證結果

用Rust來計算下結果并驗證。源碼在第7節(jié)獲取。

PS:以下代碼使用NIGHTLY版本構建??梢允褂肦ust在線編譯器去編譯(https://play.rust-lang.org/)。

image-20220118204801286

結果說明一切!

0x06 小結

其實,不僅僅只有0.1 + 0.2 會出現(xiàn)這種結果,像類似的0.1 + 0.7 = 0.7999999999999999等等,只要十進制轉化為二進制是無限的,就會出現(xiàn)這種結果。其實這種現(xiàn)象也不僅僅在Rust存在,在常見的Java,C,JavaScript等等都會存在以上現(xiàn)象,這也是一個老生常談的話題了。

0x07 源碼

#![feature(core_intrinsics)]
use std::intrinsics::powf64;

fn main() {
    let a = 0.1;
    let b = 0.2;
    println!("{} + {} = {}", a, b, a + b);

    calc();
}

fn calc() {
    unsafe {
        let s = "0100110011001100110011001100110011001100110011001100111";
        let mut index = -1_f64;

        let mut sum = 0_f64;
        for i in s.chars() {
            let temp = char::to_digit(i, 10).unwrap() as f64;
            let k = powf64(2_f64, index);
            sum += temp * k;
            index -= 1_f64;
        }
        println!("{}", sum);
    }
}

運行結果:

0.1 + 0.2 = 0.30000000000000004
0.30000000000000004
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容