內容

Dart 中的數字

Dart 應用程式經常鎖定多個平台。例如,Flutter 應用程式可能鎖定 iOS、Android 和網頁。只要應用程式不依賴於特定於平台的函式庫,或以依賴於平台的方式使用數字,程式碼就可以相同。

此頁面詳細說明原生數字實作與網路數字實作之間的差異,以及如何撰寫程式碼,讓這些差異不重要。

Dart 數字表示法

#

在 Dart 中,所有數字都是常見 Object 類型階層的一部分,而且有兩個具體的、使用者可見的數字類型:表示整數值的 int 和表示小數值的 double

Object is the parent of num, which is the parent of int and double

這些數字類型會依據平台而有不同的隱藏實作。特別是,Dart 有兩種截然不同的目標類型可編譯為

  • 原生:最常見的是 64 位元行動裝置或桌上型電腦處理器。
  • 網路:JavaScript 作為主要的執行引擎。

下表顯示 Dart 數字的常見實作方式

表示法原生 int原生 double網路 int網路 double
64 位元有號二補數
64 位元浮點數

對於原生目標,你可以假設 int 對應到有號 64 位元整數表示法,而 double 對應到與底層處理器相符的 64 位元 IEEE 浮點數表示法。

但在網路中,Dart 編譯為 JavaScript 並與之互通,只有一個數字表示法:64 位元雙精度浮點數值。為了效率,Dart 將 intdouble 都對應到這個單一表示法。可見的類型階層保持不變,但底層的隱藏實作類型不同且相互交織。

下圖說明原生和網路目標的特定於平台的類型(藍色)。正如圖中所示,原生目標上 int 的具體類型只實作 int 介面。然而,網路目標上 int 的具體類型實作 intdouble

Implementation classes vary by platform; for JavaScript, the class that implements int also implements double

網路上的 int 以沒有小數部分的雙精度浮點值表示。實際上,這運作得很好:雙精度浮點提供 53 位元的整數精度。但是,int 值永遠也是 double 值,這可能會帶來一些驚喜。

行為差異

#

大多數整數和雙精度運算基本上有相同的行為。然而,有重要的差異,特別是當你的程式碼對精度、字串格式化或底層執行時間類型有嚴格的期望時。

當運算結果不同時,如本節所述,行為是 特定於平台可能會變更

精度

#

下表說明由於精度而導致一些數字表達式不同的方式。在此,math 代表 dart:math 函式庫,而 math.pow(2, 53) 是 253

在網路上,整數在超過 53 位元後會失去精度。特別是,由於捨去,253 和 253+1 會對應到相同的值。在原生環境中,這些值仍然可以區分,因為原生數字有 64 位元,其中 63 位元用於值,1 位元用於符號。

比較 263-1 和 263 時,會看到溢出的效果。在原生環境中,後者會溢出為 -263,正如二補數運算所預期的那樣。在網路上,這些值不會溢出,因為它們的表示方式不同;由於精度損失,它們是近似值。

表達式原生網路
math.pow(2, 53) - 190071992547409919007199254740991
math.pow(2, 53)90071992547409929007199254740992
math.pow(2, 53) + 190071992547409939007199254740992
math.pow(2, 62)46116860184273879044611686018427388000
math.pow(2, 63) - 192233720368547758079223372036854776000
math.pow(2, 63)-92233720368547758089223372036854776000
math.pow(2, 64)018446744073709552000

身分

#

在原生平台上,doubleint 是不同的類型:沒有任何值可以同時是 doubleint。在網路上,這並非如此。由於這個差異,身分識別在不同平台之間可能不同,儘管相等性 (==) 沒有不同。

下表顯示一些使用相等性和身分識別的表達式。相等性表達式在原生環境和網路上是相同的;身分識別表達式通常不同。

表達式原生網路
1.0 == 1truetrue
identical(1.0, 1)falsetrue
0.0 == -0.0truetrue
identical(0.0, -0.0)falsetrue
double.nan == double.nanfalsefalse
identical(double.nan, double.nan)truefalse
double.infinity == double.infinitytruetrue
identical(double.infinity, double.infinity)truetrue

型別和型別檢查

#

在網路上,底層的 int 類型就像 double 的子類型:它是一個沒有小數部分的雙精度值。事實上,在網路上對 x is int 形式的類型檢查,如果 x 是小數部分為零值的一個數字 (double),則會傳回 true。

因此,下列內容在網路上為 true

  • 所有 Dart 數字 (num 類型的值) 都是 double
  • 一個 Dart 數字可以同時是 doubleint

這些事實會影響 is 檢查和 runtimeType 屬性。一個副作用是 double.infinity 被解釋為 int。因為這是一個特定於平台的行為,所以它可能會在未來改變。

表達式原生網路
1 是 inttruetrue
1 是 doublefalsetrue
1.0 是 intfalsetrue
1.0 是 doubletruetrue
(0.5 + 0.5) 是 intfalsetrue
(0.5 + 0.5) 是 doubletruetrue
3.14 是 intfalsefalse
3.14 是 doubletruetrue
double.infinity 是 intfalsetrue
double.nan 是 intfalsefalse
1.0.runtimeTypedoubleint
1.runtimeTypeintint
1.5.runtimeTypedoubledouble

位元運算

#

由於網路上效能的理由,int 上的位元運算 (&|^~) 和位移 (<<>>>>>) 算子使用原生 JavaScript 等價物。在 JavaScript 中,運算元會被截斷成 32 位元整數,並視為未簽署。這種處理方式可能會對較大的數字產生令人驚訝的結果。特別是,如果運算元為負值或不符合 32 位元,它們很可能會在原生和網路之間產生不同的結果。

下表顯示當運算元為負值或接近 32 位元時,原生和網路平台如何處理位元運算和位移算子

表達式原生網路
-1 >> 0-14294967295
-1 ^ 2-34294967293
math.pow(2, 32).toInt()42949672964294967296
math.pow(2, 32).toInt() >> 121474836480
(math.pow(2, 32).toInt()-1) >> 121474836472147483647

字串表示法

#

在網路上,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 程式碼已在原生和網路平台上執行多年,而數字實作差異很少會造成問題。常見的典型程式碼(例如:反覆運算範圍內的小整數和索引清單)的行為相同。

如果您有測試或斷言來比較字串結果,請以平台有彈性的方式撰寫它們。例如,假設您正在測試具有嵌入數字的字串表達式的值

dart
void main() {
  var count = 10.0 * 2;
  var message = "$count cows";
  if (message != "20.0 cows") throw Exception("Unexpected: $message");
}

前述程式碼在原生平台上會執行成功,但在網路平台上會擲回,因為在網路平台上 message"20 cows"(沒有小數點)。作為替代方案,您可以將條件寫成如下,這樣在原生和網路平台上都會通過

dart
if (message != "${20.0} cows") throw ...

對於位元操作,請考慮明確地對 32 位元塊進行操作,這些塊在所有平台上都一致。若要強制對 32 位元塊進行有號解譯,請使用 int.toSigned(32)

對於精度很重要的其他情況,請考慮其他數字類型。BigInt 類型在原生和網路平台上都提供任意精度的整數。fixnum 套件提供嚴格的 64 位元有號數字,即使在網路平台上也是如此。不過,請小心使用這些類型:它們通常會導致程式碼大幅變大且變慢。