目錄

Dart 中的數字

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

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

Dart 數字表示法

#

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

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

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

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

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

表示法原生 int原生 doubleWeb intWeb double
64 位元帶號二補數
64 位元浮點數

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

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

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

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 上,這些值不會溢出,因為它們的表示方式不同;它們是由於精度損失而產生的近似值。

表達式原生Web
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。在 Web 上,情況並非如此。由於這種差異,識別在平台之間可能會有所不同,儘管相等性 (==) 不會有所不同。

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

表達式原生Web
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 上為 true

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

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

表達式原生Web
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 是 intfalsefalse
3.14 是 doubletruetrue
double.infinity 是 intfalsetrue
double.nan 是 intfalsefalse
1.0.runtimeTypedoubleint
1.runtimeTypeintint
1.5.runtimeTypedoubledouble

位元運算

#

為了網頁上的效能考量,在 int 上使用位元運算子 (&|^~) 和位移運算子 (<<>>>>>) 時,會使用原生 JavaScript 對應的運算。在 JavaScript 中,運算元會被截斷為 32 位元的整數,並視為無號數。這種處理方式可能會導致較大數值產生令人意外的結果。特別是當運算元為負數或不符合 32 位元時,在原生和網頁上的結果可能會有所不同。

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

表達式原生Web
-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 位元有號數,即使在網頁上也是如此。但是,請謹慎使用這些類型:它們通常會導致程式碼變得更大且更慢。