Dart 中的數字
Dart 應用程式通常以多個平台為目標。例如,Flutter 應用程式可能以 iOS、Android 和 Web 為目標。只要應用程式不依賴特定平台的函式庫,或以與平台相關的方式使用數字,程式碼就可以相同。
此頁面詳細說明原生和 Web 數字實作之間的差異,以及如何編寫程式碼,使這些差異無關緊要。
Dart 數字表示法
#在 Dart 中,所有數字都是通用 Object
類型階層的一部分,並且有兩個具體的、使用者可見的數值類型:int
代表整數值,double
代表小數值。
根據平台的不同,這些數值類型具有不同的隱藏實作。特別是,Dart 有兩種非常不同的編譯目標
- 原生: 最常見的是 64 位元行動或桌上型處理器。
- Web: JavaScript 作為主要執行引擎。
下表顯示 Dart 數字通常如何實作
表示法 | 原生 int | 原生 double | Web int | Web double |
---|---|---|---|---|
64 位元帶號二補數 | ✅ | |||
64 位元浮點數 | ✅ | ✅ | ✅ |
對於原生目標,您可以假設 int
對應到帶號 64 位元整數表示法,而 double
對應到與底層處理器匹配的 64 位元 IEEE 浮點數表示法。
但在 Web 上,Dart 編譯為 JavaScript 並與其互通,只有一個數值表示法:64 位元雙精度浮點數值。為了效率,Dart 將 int
和 double
都對應到這個單一表示法。可見的類型階層保持不變,但底層隱藏的實作類型卻不同且相互交織。
下圖說明原生和 Web 目標的平台特定類型(藍色)。如下圖所示,原生 int
的具體類型僅實作 int
介面。然而,Web 上 int
的具體類型同時實作 int
和 double
。
Web 上的 int
表示為沒有小數部分的雙精度浮點數值。實際上,這運作得相當好:雙精度浮點數提供 53 位元的整數精確度。但是,int
值也總是 double
值,這可能會導致一些意外。
行為差異
#大多數整數和雙精度算術的行為基本相同。但是,存在重要的差異,尤其是在您的程式碼對精確度、字串格式或底層執行階段類型有嚴格的期望時。
當算術結果不同時,如此章節所述,該行為是平台特定的且可能會變更。
精確度
#下表展示了一些數值表達式如何由於精確度而不同。在此,math
代表 dart:math
函式庫,而 math.pow(2, 53)
是 253。
在 Web 上,整數超過 53 位元時會失去精確度。特別是,253 和 253+1 由於截斷而對應到相同的值。在原生上,這些值仍然可以區分,因為原生數字有 64 位元 — 63 位元用於值,1 位元用於符號。
當比較 263-1 和 263 時,溢出的影響是可見的。在原生上,後者會溢出為 -263,這與二補數算術的預期相同。在 Web 上,這些值不會溢出,因為它們的表示方式不同;它們是由於精度損失而產生的近似值。
表達式 | 原生 | Web |
---|---|---|
math.pow(2, 53) - 1 | 9007199254740991 | 9007199254740991 |
math.pow(2, 53) | 9007199254740992 | 9007199254740992 |
math.pow(2, 53) + 1 | 9007199254740993 | 9007199254740992 |
math.pow(2, 62) | 4611686018427387904 | 4611686018427388000 |
math.pow(2, 63) - 1 | 9223372036854775807 | 9223372036854776000 |
math.pow(2, 63) | -9223372036854775808 | 9223372036854776000 |
math.pow(2, 64) | 0 | 18446744073709552000 |
識別
#在原生平台上,double
和 int
是不同的類型:沒有值可以同時是 double
和 int
。在 Web 上,情況並非如此。由於這種差異,識別在平台之間可能會有所不同,儘管相等性 (==
) 不會有所不同。
下表顯示一些使用相等性和識別的表達式。相等性表達式在原生和 Web 上相同;識別表達式通常不同。
表達式 | 原生 | Web |
---|---|---|
1.0 == 1 | true | true |
identical(1.0, 1) | false | true |
0.0 == -0.0 | true | true |
identical(0.0, -0.0) | false | true |
double.nan == double.nan | false | false |
identical(double.nan, double.nan) | true | false |
double.infinity == double.infinity | true | true |
identical(double.infinity, double.infinity) | true | true |
類型和類型檢查
#在 Web 上,底層 int
類型就像 double
的子類型:它是沒有小數部分的雙精度值。事實上,如果 x
是小數部分值為零的數字 (double
),則 Web 上 x is int
形式的類型檢查會傳回 true。
因此,以下在 Web 上為 true
- 所有 Dart 數字(類型為
num
的值)都是double
。 - Dart 數字可以同時是
double
和int
。
這些事實會影響 is
檢查和 runtimeType
屬性。副作用是 double.infinity
會被解譯為 int
。由於這是平台特定的行為,因此未來可能會變更。
表達式 | 原生 | Web |
---|---|---|
1 is int | true | true |
1 is double | false | true |
1.0 is int | false | true |
1.0 is double | true | true |
(0.5 + 0.5) is int | false | true |
(0.5 + 0.5) is double | true | true |
3.14 是 int | false | false |
3.14 是 double | true | true |
double.infinity 是 int | false | true |
double.nan 是 int | false | false |
1.0.runtimeType | double | int |
1.runtimeType | int | int |
1.5.runtimeType | double | double |
位元運算
#為了網頁上的效能考量,在 int
上使用位元運算子 (&
、|
、^
、~
) 和位移運算子 (<<
、>>
、 >>>
) 時,會使用原生 JavaScript 對應的運算。在 JavaScript 中,運算元會被截斷為 32 位元的整數,並視為無號數。這種處理方式可能會導致較大數值產生令人意外的結果。特別是當運算元為負數或不符合 32 位元時,在原生和網頁上的結果可能會有所不同。
下表顯示當運算元為負數或接近 32 位元時,原生和網頁平台如何處理位元和位移運算子
表達式 | 原生 | Web |
---|---|---|
-1 >> 0 | -1 | 4294967295 |
-1 ^ 2 | -3 | 4294967293 |
math.pow(2, 32).toInt() | 4294967296 | 4294967296 |
math.pow(2, 32).toInt() >> 1 | 2147483648 | 0 |
(math.pow(2, 32).toInt()-1) >> 1 | 2147483647 | 2147483647 |
字串表示法
#在網頁上,Dart 通常會委託 JavaScript 將數字轉換為字串 (例如,用於 print
)。下表展示了如何轉換第一欄中的表達式可能會導致不同的結果。
表達式 | 原生 toString() | 網頁 toString() |
---|---|---|
1 | "1" | "1" |
1.0 | "1.0" | "1" |
(0.5 + 0.5) | "1.0" | "1" |
1.5 | "1.5" | "1.5" |
-0 | "0" | "-0.0" |
math.pow(2, 0) | "1" | "1" |
math.pow(2, 80) | "0" | "1.2089258196146292e+24" |
您應該怎麼做?
#通常,您不需要更改您的數值程式碼。Dart 程式碼已經在原生和網頁平台運行多年,數字實作差異很少會是問題。常見的典型程式碼 (例如,迭代一系列小整數和為列表建立索引) 的行為相同。
如果您有比較字串結果的測試或斷言,請以平台彈性的方式編寫它們。例如,假設您正在測試嵌入數字的字串表達式的值
void main() {
var count = 10.0 * 2;
var message = "$count cows";
if (message != "20.0 cows") throw Exception("Unexpected: $message");
}
前面的程式碼在原生平台上成功,但在網頁上會拋出錯誤,因為在網頁上 message
是 "20 cows"
(沒有小數)。 作為替代方案,您可以將條件寫成如下,使其在原生和網頁平台上都能通過
if (message != "${20.0} cows") throw ...
對於位元操作,請考慮明確地對 32 位元的區塊進行操作,這樣在所有平台上都是一致的。若要強制將 32 位元的區塊解釋為有號數,請使用 int.toSigned(32)
。
對於其他需要精確度的情況,請考慮其他數值類型。BigInt
類型在原生和網頁上都提供任意精度的整數。fixnum
套件提供嚴格的 64 位元有號數,即使在網頁上也是如此。但是,請謹慎使用這些類型:它們通常會導致程式碼變得更大且更慢。
除非另有說明,本網站上的文件反映的是 Dart 3.6.0。頁面最後更新於 2024-12-10。 檢視原始碼 或 回報問題。