Rust 針對浮點數有兩種型別f32
和 f64
,分別佔有 32 位元與 64 位元的大小。
所有的浮點數型別第一個位元都是用來記錄正負號,不像整數有沒正負號的型別。
浮點數是依照 IEEE-754
所定義的,f32
型別對應單精度浮點數(經度約小數點後6位),而 f64
是雙精度浮點數(經度約小數點後15位)。
因為精確度、和其他語言的兼容性考量,以及現代處理器對於f64
運算有做優化所以也不會有明顯的性能損失, Rust 預設的浮點數型別為f64
。
Rust 支援所有想得到的數值型別基本運算:加法、減法、乘法、除法和取餘。
整數除法會取最接近零的下界數值。
以下舉例,另外寫一個函數來把結果的 type 印出來,VSCode 的 extension rust-analyzer
也會直接提示型別,一般情況強型別語言因為型別都定義好,也用不到這種函數就是。
首先看整數,基本上整數沒特別指定,Rust 會用預設i32
。
use std::any::type_name;
fn print_type_of<T>(_: &T) {
println!("Type is: {}", type_name::<T>());
}
fn main() {
// 加法
let sun = 1 + 2;
println!("{}", sun); // 3
print_type_of(&sun); // Type is: i32
// 減法
let difference = 5 - 10;
println!("{}", difference); // -5
print_type_of(&difference); // Type is: i32
// 乘法
let product = 4 * 30;
println!("{}", product); // 120
print_type_of(&product); // Type is: i32
// 除法
let quotient = 5 / 2;
println!("{}", quotient); // 2
print_type_of("ient); // Type is: i32
let negative_quotient = -5 / 2;
println!("{}", negative_quotient); // -2
print_type_of(&negative_quotient); // Type is: i32
// 取餘
let remainder = -8 % 5;
println!("{}", remainder); // -3
print_type_of(&remainder); // Type is: i32
}
比較特別是整數除法會向 0 取整,型別不會改變。
然後我想說看能不能整數和小數相加,畢竟 JavaScript 可以XD
fn main() {
let sum = 1.1 + 10;
println!("{}", sum);
}
結果編譯階段就會報錯了,一定要同類型的數字才能做運算。
$ cargo run
error[E0277]: cannot add an integer to a float
--> src/main.rs:8:19
|
8 | let sum = 1.1 + 10;
| ^ no implementation for `{float} + {integer}`
|
= help: the trait `Add<{integer}>` is not implemented for `{float}`
= help: the following other types implement trait `Add<Rhs>`:
<&'a f128 as Add<f128>>
<&'a f16 as Add<f16>>
<&'a f32 as Add<f32>>
<&'a f64 as Add<f64>>
<&'a i128 as Add<i128>>
<&'a i16 as Add<i16>>
<&'a i32 as Add<i32>>
<&'a i64 as Add<i64>>
and 56 others
For more information about this error, try `rustc --explain E0277`.
error: could not compile `types_float` (bin "types_float") due to 1 previous error
把 10
加上小數點就可以,或是用as
關鍵字做型別轉換。
fn main() {
let sum = 1.1 + 10 as f64;
println!("{}", sum); // 1.1
}
接著看一下有小數情況的運算:
use std::any::type_name;
fn print_type_of<T>(_: &T) {
println!("Type is: {}", type_name::<T>());
}
fn main() {
// 加法
let sun = 0.1 + 0.2;
println!("{}", sun); // 0.30000000000000004
print_type_of(&sun); // Type is: f64
// 減法
let difference = 5.5 - 10.387;
println!("{}", difference); // -4.8870000000000005
print_type_of(&difference); // Type is: f64
// 乘法
let product = 4.7 * 30.2;
println!("{}", product); // 141.94
print_type_of(&product); // Type is: f64
// 除法
let quotient = 5.2 / 2.1;
println!("{}", quotient); // 2.4761904761904763
print_type_of("ient); // Type is: f64
let negative_quotient = -5.2 / 2.1;
println!("{}", negative_quotient); // -2.4761904761904763
print_type_of(&negative_quotient); // Type is: f64
// 取餘
let remainder = -8.5 % 5.72;
println!("{}", remainder); // -2.7800000000000002
print_type_of(&remainder); // Type is: f64
}
不意外有浮點數誤差,運算的結果也還是 f64
,除法的部分當然也不會像整數那樣把結果取整了。
經過測試可以知道 Rust 很重要的特性:類型轉換要是顯式的,Rust不會自己轉換型別。
如果運算超過範圍,浮點數也會有溢位,會產生 +Infinity(inf)
或 -Infinity(-inf)
,分別代表正無窮大和負無窮大:
fn main() {
let positive_infinity = 1.0 / 0.0;
println!("1.0 / 0.0 = {}", positive_infinity); // inf
print_type_of(&positive_infinity); // Type is: f64
let negative_infinity = -1.0 / 0.0;
println!("-1.0 / 0.0 = {}", negative_infinity); // -inf
print_type_of(&negative_infinity); // Type is: f64
let large_number = 1e308 * 1e308; // 超過了 f64 的表示範圍
println!("1e308 * 1e308 = {}", large_number); // inf
print_type_of(&large_number); // Type is: f64
}
可以看到型別還是 f64
,也可以和其他f64
做運算,不過結果不那麼直觀:
fn main() {
let positive_infinity = f64::INFINITY;
let negative_infinity = f64::NEG_INFINITY;
println!("positive_infinity + 10 = {}", positive_infinity + 10.0); // inf
println!("positive_infinity * -2 = {}", positive_infinity * -2.0); // -inf
println!("10 / positive_infinity = {}", 10.0 / positive_infinity); // 0
println!("positive_infinity / positive_infinity = {}", positive_infinity / positive_infinity); // NaN
}
NaN
(Not a Number)代表計算結果無意義或未定義。NaN
不等於任何值,甚至不等於自身:
fn main() {
assert_eq!(f64::NAN, f64::NAN);
}
assertion `left == right` failed
left: NaN
right: NaN
除了 Infinity
互除以外,0 互除的結果也可能 NaN
,不過要注意型別是什麼。
如果 0 是整數的型別編譯會沒辦法通過,而且如果是在 run time 遇到這個情況會讓程式崩潰,所以在 Rust 中進行整數除法時,最好檢查除數是否為 0。
fn main() {
let nan = 0_i32 / 0_i32;
println!("{}", nan);
}
$ cargo run
error: this operation will panic at runtime
--> src/main.rs:47:15
|
47 | let nan = 0_i32 / 0_i32;
| ^^^^^^^^^^^^^ attempt to divide `0_i32` by zero
|
= note: `#[deny(unconditional_panic)]` on by default
error: could not compile `types_float` (bin "types_float") due to 1 previous error
如果是浮點數的話結果會是 NaN
,不會報錯,和Infinity
一樣型別都會是原本的浮點數型別(f64
或f32
),不同型別不論是什麼都沒辦法做運算。另外NaN
和其他數字處理結果都會是NaN
。
fn main() {
let positive_infinity = f32::INFINITY;
let nan = 0_f32 / 0_f32;
println!("nan: {}", nan);
println!("nan + 1.0 = {}", nan + 1.0);
println!("nan + positive_infinity = {}", nan + positive_infinity);
}
$ cargo run
nan: NaN
nan + 1.0 = NaN
nan + positive_infinity = NaN
Rust 有對應的方法來檢查這兩種情況:
fn main() {
let x: f64 = 1.0 / 0.0;
let y: f64 = 0.0 / 0.0;
if x.is_infinite() {
println!("x is infinite: {}", x);
}
if y.is_nan() {
println!("y is NaN: {}", y);
}
}
這邊比較特別的是變數一定要指定型別才能用這兩種方法,不然會有以下錯誤訊息,原因是沒有指定的話 Rust 無法確定是f32
還是f64
的方法。
error[E0689]: can't call method `is_infinite` on ambiguous numeric type `{float}`
今天在寫的時候其實很多地方想要故意寫錯,例如不同型別運算等,但 Rust 的編譯器都會報錯,和 JavaScript 比起來非常繁瑣,不過滿準的型別推斷和清楚的錯誤訊息緩和滿多在修正編譯錯誤時的開發體驗。
另外和 JavaScript 滿大的差異是明確把整數和小數的運算區隔開了,加上型別需要顯示轉換,除了能讓開發者知道每一步驟的狀態和行為,也能減少運算中的不確定性,避免潛在錯誤,也是 Rust 安全性優勢之一。