目次

Dartの数値

Dartアプリは、多くの場合、複数のプラットフォームをターゲットにしています。たとえば、FlutterアプリはiOS、Android、およびWebをターゲットにする可能性があります。アプリがプラットフォーム固有のライブラリに依存したり、プラットフォームに依存する方法で数値を使用しない限り、コードは同じにすることができます。

このページでは、ネイティブとWebの数値実装の違い、およびそれらの違いが問題にならないようにコードを記述する方法について詳しく説明します。

Dartの数値表現

#

Dartでは、すべての数値は共通のObject型階層の一部であり、2つの具体的なユーザー可視の数値型があります。整数値を表すintと、小数値を表すdoubleです。

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

プラットフォームによっては、これらの数値型には異なる非表示の実装があります。特に、Dartには、コンパイル先の非常に異なる2つのタイプのターゲットがあります。

  • ネイティブ:最も一般的なのは、64ビットのモバイルまたはデスクトッププロセッサーです。
  • Web:プライマリ実行エンジンとしてのJavaScript。

次の表は、Dartの数値が通常どのように実装されるかを示しています。

表現ネイティブintネイティブdoubleWebintWebdouble
64ビット符号付き2の補数
64ビット浮動小数点

ネイティブターゲットの場合、intは符号付き64ビット整数表現にマッピングされ、doubleは基盤となるプロセッサーと一致する64ビットIEEE浮動小数点表現にマッピングされると想定できます。

しかし、DartがJavaScriptにコンパイルされ、JavaScriptと相互運用するWebでは、単一の数値表現があります。それは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値でもあるため、驚きが生じる可能性があります。

動作の違い

#

ほとんどの整数とdoubleの算術演算は、基本的に同じ動作をします。ただし、特にコードが精度、文字列フォーマット、または基盤となるランタイム型について厳密な期待を持っている場合、重要な違いがあります。

このセクションで説明するように、算術演算の結果が異なる場合、その動作はプラットフォーム固有であり、変更される可能性があります

精度

#

次の表は、精度によっていくつかの数値式がどのように異なるかを示しています。ここで、mathdart:mathライブラリを表し、math.pow(2, 53)は253です。

Webでは、整数は53ビットを超えると精度が失われます。特に、253と253+1は、切り捨てのために同じ値にマッピングされます。ネイティブでは、これらの値は、ネイティブ数値が64ビット(値に63ビット、符号に1ビット)を持っているため、区別できます。

オーバーフローの効果は、263-1と263を比較するとわかります。ネイティブでは、後者は2の補数演算で予想されるように-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では次のことが当てはまります。

  • すべてのDart数値(num型の値)はdoubleです。
  • Dartの数値は、同時にdoubleintの両方になることができます。

これらの事実は、isチェックとruntimeTypeプロパティに影響を与えます。副作用として、double.infinityintとして解釈されます。これはプラットフォーム固有の動作であるため、将来変更される可能性があります。

ネイティブ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()WebtoString()
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ではmessageがWebでは"20 cows"(小数点なし)となるため、例外をスローします。代替として、以下の条件のように記述すると、ネイティブとWebの両方のプラットフォームでパスします。

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

ビット操作の場合、すべてのプラットフォームで一貫性のある32ビットのチャンクで明示的に操作することを検討してください。32ビットチャンクの符号付き解釈を強制するには、int.toSigned(32)を使用してください。

精度が重要な他のケースでは、他の数値型を検討してください。BigInt型は、ネイティブとWebの両方で任意の精度の整数を提供します。fixnumパッケージは、Web上でも厳密な64ビット符号付き数値を提供します。ただし、これらの型は慎重に使用してください。多くの場合、コードが著しく大きくなり、遅くなる可能性があります。