跳到主要內容

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 的子型別:它是一個沒有小數部分的雙精度值。實際上,在 Web 上形式為 x is int 的型別檢查會在 x 是一個小數部分為零的數字(double)時返回 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 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 平臺如何處理按位和移位運算子

表示式原生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

字串表示

#

在 Web 上,Dart 通常委託給 JavaScript 將數字轉換為字串(例如,用於 print)。下表演示了將第一列中的表示式轉換為字串可能會導致結果不同。

表示式原生 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 位有符號數。然而,請謹慎使用這些型別:它們通常會導致程式碼體積更大、執行更慢。