Dartの数値
Dartアプリは、多くの場合、複数のプラットフォームをターゲットにしています。たとえば、FlutterアプリはiOS、Android、およびWebをターゲットにする可能性があります。アプリがプラットフォーム固有のライブラリに依存したり、プラットフォームに依存する方法で数値を使用しない限り、コードは同じにすることができます。
このページでは、ネイティブとWebの数値実装の違い、およびそれらの違いが問題にならないようにコードを記述する方法について詳しく説明します。
Dartの数値表現
#Dartでは、すべての数値は共通のObject
型階層の一部であり、2つの具体的なユーザー可視の数値型があります。整数値を表すint
と、小数値を表すdouble
です。
プラットフォームによっては、これらの数値型には異なる非表示の実装があります。特に、Dartには、コンパイル先の非常に異なる2つのタイプのターゲットがあります。
- ネイティブ:最も一般的なのは、64ビットのモバイルまたはデスクトッププロセッサーです。
- Web:プライマリ実行エンジンとしてのJavaScript。
次の表は、Dartの数値が通常どのように実装されるかを示しています。
表現 | ネイティブint | ネイティブdouble | Webint | Webdouble |
---|---|---|---|---|
64ビット符号付き2の補数 | ✅ | |||
64ビット浮動小数点 | ✅ | ✅ | ✅ |
ネイティブターゲットの場合、int
は符号付き64ビット整数表現にマッピングされ、double
は基盤となるプロセッサーと一致する64ビットIEEE浮動小数点表現にマッピングされると想定できます。
しかし、DartがJavaScriptにコンパイルされ、JavaScriptと相互運用するWebでは、単一の数値表現があります。それは64ビット倍精度浮動小数点値です。効率上の理由から、Dartはint
とdouble
の両方をこの単一の表現にマッピングします。可視の型階層は同じままですが、基盤となる非表示の実装型は異なり、絡み合っています。
次の図は、ネイティブターゲットとWebターゲットのプラットフォーム固有の型(青色)を示しています。図が示すように、ネイティブのint
の具体的な型は、int
インターフェースのみを実装します。ただし、Webのint
の具体的な型は、int
とdouble
の両方を実装します。
Web上のint
は、小数部がない倍精度浮動小数点値として表現されます。実際には、これは非常にうまく機能します。倍精度浮動小数点では、53ビットの整数精度が提供されます。ただし、int
値は常にdouble
値でもあるため、驚きが生じる可能性があります。
動作の違い
#ほとんどの整数とdoubleの算術演算は、基本的に同じ動作をします。ただし、特にコードが精度、文字列フォーマット、または基盤となるランタイム型について厳密な期待を持っている場合、重要な違いがあります。
このセクションで説明するように、算術演算の結果が異なる場合、その動作はプラットフォーム固有であり、変更される可能性があります。
精度
#次の表は、精度によっていくつかの数値式がどのように異なるかを示しています。ここで、math
はdart: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) - 1 | 9007199254740991 | 9007199254740991 |
math.pow(2, 53) | 9007199254740992 | 9007199254740992 |
math.pow(2, 53) + 1 | 9007199254740993 | 9007199254740992 |
math.pow(2, 62) | 4611686018427387904 | 4611686018427388000 |
math.pow(2, 63) - 1 | 9223372036854775807 | 9223372036854776000 |
math.pow(2, 63) | -9223372036854775808 | 9223372036854776000 |
math.pow(2, 64) | 0 | 18446744073709552000 |
同一性
#ネイティブプラットフォームでは、double
とint
は異なる型です。どの値も、同時にdouble
とint
の両方になることはできません。Webでは、それは当てはまりません。この違いのため、同一性はプラットフォーム間で異なる場合がありますが、等価性(==
)は異なりません。
次の表に、等価性と同一性を使用するいくつかの式を示します。等価性の式はネイティブとWebで同じです。同一性の式は通常異なります。
式 | ネイティブ | Web |
---|---|---|
1.0 == 1 | true | true |
identical(1.0, 1) | false | true |
0.0 == -0.0 | true | true |
identical(0.0, -0.0) | false | true |
double.nan == double.nan | false | false |
identical(double.nan, double.nan) | true | false |
double.infinity == double.infinity | true | true |
identical(double.infinity, double.infinity) | true | true |
型と型チェック
#Webでは、基盤となるint
型はdouble
のサブタイプのようです。小数部がない倍精度値です。実際、Webでのx is int
形式の型チェックは、x
が小数部がゼロの値を持つ数値(double
)の場合にtrueを返します。
その結果、Webでは次のことが当てはまります。
- すべてのDart数値(
num
型の値)はdouble
です。 - Dartの数値は、同時に
double
とint
の両方になることができます。
これらの事実は、is
チェックとruntimeType
プロパティに影響を与えます。副作用として、double.infinity
はint
として解釈されます。これはプラットフォーム固有の動作であるため、将来変更される可能性があります。
式 | ネイティブ | Web |
---|---|---|
1 is int | true | true |
1 is double | false | true |
1.0 is int | false | true |
1.0 is double | true | true |
(0.5 + 0.5) is int | false | true |
(0.5 + 0.5) is double | true | true |
3.14 is int | false | false |
3.14 is double | true | true |
double.infinity is int | false | true |
double.nan is int | false | false |
1.0.runtimeType | double | int |
1.runtimeType | int | int |
1.5.runtimeType | double | double |
ビット演算
#Webでのパフォーマンス上の理由から、int
に対するビット演算(&
、|
、^
、~
)およびシフト演算子(<<
、>>
、>>>
)は、ネイティブJavaScriptと同等のものを使用します。JavaScriptでは、オペランドは符号なしとして扱われる32ビット整数に切り捨てられます。この処理により、大きい数値で驚くべき結果が生じる可能性があります。特に、オペランドが負の場合、または32ビットに収まらない場合、ネイティブとWebの間で異なる結果が生成される可能性が高くなります。
次の表は、オペランドが負であるか、32ビットに近い場合に、ネイティブプラットフォームとWebプラットフォームがビット演算子とシフト演算子をどのように扱うかを示しています。
式 | ネイティブ | Web |
---|---|---|
-1 >> 0 | -1 | 4294967295 |
-1 ^ 2 | -3 | 4294967293 |
math.pow(2, 32).toInt() | 4294967296 | 4294967296 |
math.pow(2, 32).toInt() >> 1 | 2147483648 | 0 |
(math.pow(2, 32).toInt()-1) >> 1 | 2147483647 | 2147483647 |
文字列表現
#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プラットフォームの両方で実行されており、数値実装の違いはめったに問題になりません。小さな整数の範囲を反復処理したり、リストにインデックスを付けたりするなどの、一般的な典型的なコードは、同じように動作します。
文字列の結果を比較するテストやアサーションがある場合は、プラットフォームに依存しない方法で記述してください。たとえば、数値が埋め込まれた文字列式の値をテストするとします。
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の両方のプラットフォームでパスします。
if (message != "${20.0} cows") throw ...
ビット操作の場合、すべてのプラットフォームで一貫性のある32ビットのチャンクで明示的に操作することを検討してください。32ビットチャンクの符号付き解釈を強制するには、int.toSigned(32)
を使用してください。
精度が重要な他のケースでは、他の数値型を検討してください。BigInt
型は、ネイティブとWebの両方で任意の精度の整数を提供します。fixnum
パッケージは、Web上でも厳密な64ビット符号付き数値を提供します。ただし、これらの型は慎重に使用してください。多くの場合、コードが著しく大きくなり、遅くなる可能性があります。
特に明記されていない限り、このサイトのドキュメントはDart 3.5.3を反映しています。ページの最終更新日は2024-02-07です。 ソースを表示 または問題を報告する。