跳到主要內容

Dart 中的數字

Dart 應用程式通常以多個平台為目標。例如,Flutter 應用程式可能以 iOS、Android 和 Web 為目標。程式碼可以相同,只要應用程式不依賴平台特定的函式庫或以平台相依的方式使用數字。

此頁面詳細介紹了 Native 和 Web 數字實作之間的差異,以及如何編寫程式碼,使這些差異無關緊要。

Dart 數字表示法

#

在 Dart 中,所有數字都是通用 `Object` 型別階層的一部分,並且有兩種具體的、使用者可見的數值型別:`int`,表示整數值,以及 `double`,表示小數值。

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

根據平台,這些數值型別具有不同的、隱藏的實作。特別是,Dart 有兩種非常不同的編譯目標:

  • 原生 (Native): 通常是 64 位元行動或桌上型處理器。
  • Web: JavaScript 作為主要執行引擎。

下表顯示 Dart 數字通常如何實作:

表示法Native intNative doubleWeb intWeb double
64 位元有號二補數
64 位元浮點數

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

但在 Web 上,Dart 編譯到 JavaScript 並與之互通,只有一種數值表示法:64 位元雙精度浮點數值。為了效率,Dart 將 `int` 和 `double` 都對應到這種單一表示法。可見的型別階層保持不變,但底層隱藏的實作型別是不同的且相互交織的。

下圖說明了原生和 Web 目標的平台特定型別(藍色)。如圖所示,原生平台上 `int` 的具體型別僅實作 `int` 介面。然而,Web 平台上 `int` 的具體型別同時實作 `int` 和 `double`。

Implementation classes vary by platform; for JavaScript, the class that implements int also implements 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 上,這些值不會溢位,因為它們的表示方式不同;它們是由於精度損失而產生的近似值。

表達式NativeWeb
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

識別

#

在原生平台上,`double` 和 `int` 是不同的型別:沒有值可以同時是 `double` 和 `int`。在 Web 上,情況並非如此。由於這種差異,識別在平台之間可能會有所不同,儘管相等性 (`==`) 不會。

下表顯示了一些使用相等性和識別的表達式。相等性表達式在原生平台和 Web 上是相同的;識別表達式通常是不同的。

表達式NativeWeb
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

型別與型別檢查

#

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

因此,以下在 Web 上為真:

  • 所有 Dart 數字(`num` 型別的值)都是 `double`。
  • 一個 Dart 數字可以同時是 `double` 和 `int`。

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

表達式NativeWeb
1 is inttruetrue
1 is doublefalsetrue
1.0 is intfalsetrue
1.0 is doubletruetrue
(0.5 + 0.5) is intfalsetrue
(0.5 + 0.5) is doubletruetrue
3.14 is intfalsefalse
3.14 is doubletruetrue
double.infinity is intfalsetrue
double.nan is intfalsefalse
1.0.runtimeTypedoubleint
1.runtimeTypeintint
1.5.runtimeTypedoubledouble

位元運算

#

為了 Web 上的效能考量,`int` 上的位元運算 (`&`, `|`, `^`, `~`) 和位移 (`<<`,`>>`, `>>>`) 運算子使用原生 JavaScript 等效項。在 JavaScript 中,運算元被截斷為 32 位元整數,並被視為無號數。這種處理方式可能會導致較大數字出現令人驚訝的結果。特別是,如果運算元是負數或不適合 32 位元,它們很可能會在原生平台和 Web 之間產生不同的結果。

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

表達式NativeWeb
-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

字串表示法

#

在 Web 上,Dart 通常會延遲到 JavaScript 來將數字轉換為字串(例如,對於 `print`)。下表示範了轉換第一欄中的表達式如何導致不同的結果。

表達式Native toString()Web 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 程式碼已在原生平台和 Web 平台上運行多年,並且數字實作差異很少成為問題。常見的、典型的程式碼——例如迭代小整數範圍和索引清單——行為相同。

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

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

前面的程式碼在原生平台上成功,但在 Web 上拋出錯誤,因為 Web 上的 `message` 是 `"20 cows"`(沒有小數)。作為替代方案,您可以將條件寫成如下,使其在原生平台和 Web 平台上都能通過:

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

對於位元操作,請考慮顯式地對 32 位元區塊進行操作,這在所有平台上都是一致的。若要強制對 32 位元區塊進行有號解釋,請使用 `int.toSigned(32)`。

對於其他需要精確度的情況,請考慮其他數值型別。`BigInt` 型別在原生平台和 Web 平台上都提供任意精度的整數。`fixnum` 套件即使在 Web 上也提供嚴格的 64 位元有號數字。但請謹慎使用這些型別:它們通常會導致程式碼顯著變大且速度變慢。