こんにちは!Flutterでのアプリ開発や、サーバーサイドDartの構築を楽しんでいますか?
他のプログラミング言語からDartの世界に飛び込んできたとき、ふと「あれ、数字の扱いってどうなっているんだっけ?」と立ち止まる瞬間はありませんか?特に、変数宣言の基本である `var`、`final`、`const` を覚えたあと、私たちが日常的に扱う「数値(int型)」の裏側の挙動を知ると、Dartという言語の解像度がグッと上がります。
今回は、Dartの数値型がメモリ上でどう振る舞うのか、そしてビット演算や巨大な数を扱うときの「知られざるルール」を、優しく、そして本質的なところまで紐解いていきましょう。
ここをクリアすれば、Dartのデータ構造の基本はバッチリマスターできますよ!
—
1. Dartの `int` 型の正体:64bitの壁
まずは、私たちが普段何気なく使っている `int` 型の基本からおさらいです。
Dartの `int` は、プラットフォームによって表現方法が変わります。
- AOTコンパイル(ネイティブ / Flutter実機): 符号付き 64bit 整数(`int64`)として直接CPUのレジストリに乗ります。
- JITコンパイル / Web(JavaScriptへのトランスパイル): JavaScriptの仕様(倍精度浮動小数点数 `double`)に依存するため、安全に表せる整数は `-(2^53 – 1)` から `2^53 – 1` までとなります。
ネイティブ環境において、`int` は `-9,223,372,036,854,775,808` から `9,223,372,036,854,775,807` までの途方もない数値を、わずか8バイト(64bit)のメモリ空間にギュッと詰め込んで高速に処理しています。
void main() {
// 通常の整数宣言
int standardNumber = 42;
// constで宣言すると、コンパイル時定数として最適化されます
const int maxSafe = 9223372036854775807;
print(‘標準の数: $standardNumber’);
print(’64bit整数の限界値: $maxSafe’);
}
—
2. ビット演算の世界:64bitの箱で何が起きているか?
低レイヤーな処理(暗号化、画像処理、フラグ管理など)で不可欠なのがビット演算です。Dartでは `&`(AND), `|`(OR), `^`(XOR), `~`(NOT), `<<`(左シフト), `>>`(右シフト)が使えます。
ここで重要なのは、ビット演算を行うと、Dartの `int` は強制的に64bitの符号付き整数として振る舞うという点です。
イメージ図で考えてみましょう。
[ 符号ビット (1bit) ] [ データ領域 (63bit) ]
0 = 正の数, 1 = 負の数
例えば、負の数をビットシフトさせたり、桁あふれ(オーバーフロー)を起こしたりすると、予期せぬ挙動に驚くことがあります。
void main() {
// 1を左に63回シフトさせるとどうなるでしょう?
int a = 1;
int shifted = a << 63;
// 63回目のビットは「符号ビット」の領域に飛び込みます!
print(shifted); // 出力: -9223372036854775808 (最小の負の数に反転する)
}
💡 初学者が陥りやすい罠:符号付き右シフト (`>>`) と 符号なし右シフト (`>>>`)
Dart 2.14以降では、JavaScriptの `>>>` と同様に、符号を考慮せずに右へゼロ埋めシフトする `>>>`(Unsigned right shift)が導入されています。
通常の `>>` は、負の数であれば左側に `1` を埋め続けますが、`>>>` は強制的に `0` を埋めます。
void main() {
int negative = -8; // 2の補数表現でビットが立っています
print(negative >> 1); // 算術右シフト (符号を維持): -4
print(negative >>> 1); // 論理右シフト (0埋め): 巨大な正の数になる!
}
フラグ操作やバイナリパーシングを行う際、この違いを知らないとバグの原因になります。ぜひ覚えておいてくださいね。
—
3. 64bitの限界を超える:`BigInt` の登場シーン
「ブロックチェーンの計算をしている」「超巨大なIDを扱いたい」「64bitじゃ収まらない桁数の計算をしたい」
そんなとき、通常の `int` を使っていると、値が丸められたりオーバーフローしたりしてしまいます。
そこで登場するのが `BigInt` です。
`BigInt` には、64bitや32bitといったハードウェア上の制限がありません。メモリが許す限り、何桁の整数でも正確に計算できます。
`int` と `BigInt` の使い分け基準
| 特徴 | `int` | `BigInt` |
| :— | :— | :— |
| 速度 | 超高速(CPUのネイティブ命令) | やや低速(ソフトウェアによる多倍長演算) |
| メモリ | 固定的(8バイト) | 可変長(桁数に応じて消費メモリが増える) |
| サイズ制限 | 約 ±9.2 × $10^{18}$ (64bit) | 実質無制限 |
| 主な用途 | カウンター、ループ、通常のID、座標 | 暗号技術、巨大な金融計算、厳密な科学計算 |
実際に `BigInt` を使ってみましょう。
void main() {
// intの限界を超える巨大な数
int normalInt = 9223372036854775807;
print(‘intの最大値 + 1: ${normalInt + 1}’); // あれ?オーバーフローしてマイナスになる!
// BigIntなら安全に扱える
BigInt hugeNumber = BigInt.parse(‘9223372036854775808’);
BigInt evenBigger = hugeNumber BigInt.from(1000);
print(‘巨大な数: $hugeNumber’);
print(‘1000倍した数: $evenBigger’);
}
注意点として、`BigInt` 同士の演算には `+` や “ などの演算子がオーバーロードされているためそのまま使えますが、通常の `int` と直接混ぜて計算することはできません(必ず `BigInt.from()` などで型を合わせる必要があります)。
—
まとめ:Dartの数値を手のうちに収めよう
今回は、Dartの数値型が持つ「裏側の顔」について解説しました。
1. `int` は基本は64bit(プラットフォーム依存もあるが高速)。
2. ビット演算時は、符号ビットや桁あふれ(オーバーフロー)の挙動に注意する。
3. 64bitを超える圧倒的な精度が必要なときは、迷わず `BigInt` を採用する。
言語の仕様やメモリの仕組みを知ることは、一見遠回りに見えて、実は「バグのない堅牢なコード」を書くための最も近道です。
「このデータ、ひょっとしたら64bitの枠からはみ出るかも?」というアンテナを張れるようになったあなたなら、もうDartの数値扱いで迷うことはありません。
明日からのコーディングを、さらに楽しんでいきましょう!