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
為了看得更加清楚,可以看下面的圖片。

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

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

最終得出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/)。

結果說明一切!
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