【入門編】Dartの数値型における精度問題:doubleとnumの使い分けと丸め誤差の回避 – Dart コア文法・オブジェクト指向・Null安全解析バイブル

こんにちは!FlutterやDartを使った開発を楽しんでいますか?

他のプログラミング言語からDartの世界へやってくると、数値型周りで「おや?」と躓く瞬間があります。特に、小数点を扱う計算で「なぜか値が微妙にズレる…」という現象に直面したことはありませんか?

今回は、Dartの数値型(`num`, `int`, `double`)の本質と、避けて通れない「浮動小数点数の精度問題」、そしてそれを華麗に回避するベストプラクティスを、私と一緒に紐解いていきましょう。ここをクリアすれば、数値の扱いはもうバッチリマスターできますよ!

—

1. Dartの数値型の世界(`num`, `int`, `double`)

まず、Dartの数値型がどのように階層構造を持っているかを押さえましょう。

Dartでは、すべての数は `num` という抽象クラスの子供たちです。そして `num` には、整数を表す `int` と、浮動小数点数(小数)を表す `double` の2つの具象型が存在します。

[ num ] (数値の親)
/ \
[ int ] [ double ]
(整数) (浮動小数点)

`num` を使うべき場面、使わないべき場面

`num` は `int` も `double` も両方受け入れられる便利な型に見えます。「どっちが入るか分からないからとりあえず `num` にしておこう」と思いがちですが、実際の開発現場ではあまりお勧めしません。

なぜなら、コンパイラやVM(Dart VM)から見ると、型が曖昧になると最適化(JIT/AOTコンパイル時の型推論やインライン化)の恩恵を受けにくくなるからです。基本的には、整数なら `int`、小数なら `double` と明確に使い分けるのがプロの流儀です。

—

2. 誰もがハマる「doubleの精度問題」の正体

さて、本題です。次のコードを実行すると、何が出力されると思いますか?

void main() {
double a = 0.1;
double b = 0.2;
print(a + b);
}

「そりゃあ `0.3` に決まっているでしょ?」と思いますよね。
しかし、Dart(およびIEEE 754という浮動小数点規格を採用するほとんどの近代言語)の実行結果はこうなります。

0.30000000000000004

「うわっ、なんだこれ!」って思いますよね。これが丸め誤差(浮動小数点の精度問題)です。

なぜ誤差が起きるのか?(VMの裏側)

私たちの使っているコンピュータは、数を「10進数」ではなく「2進数(0と1)」で記憶しています。
整数であれば綺麗に2進数に変換できますが、`0.1` や `0.2` のような小数は、2進数に直すと「無限に続く循環小数」になってしまいます。

10進数の 0.1 = 2進数では 0.000110011001100110011…(無限ループ!)

コンピュータは無限のメモリを持っていませんから、どこかの桁で「四捨五入(丸め)」して切り捨てます。その結果、限られた精度の中で計算を行うため、最後の最後に微小なゴミ(誤差)が残ってしまうのです。

—

3. 金額計算で絶対にやってはいけないアンチパターン

この誤差の性質を知らないまま、例えば「ECサイトの合計金額の計算」などで `double` をそのまま使ってしまうと大変なことになります。

// 【絶対にしてはいけない例】
void main() {
double price1 = 100.0;
double price2 = 200.0;

// 税率計算などの複雑な浮動小数点演算が絡むと…
double total = (price1 1.08) + (price2 1.08);

print(total); // 324.00000000000006 などになる危険性

if (total == 324.0) {
print(‘お会計ぴったりです!’);
} else {
print(‘あれ?金額が一致しません(バグ発生)’);
}
}

金融システムやECの決済で「1円(あるいは0.01ドル)合わない」というのは致命的なバグになりますよね。では、どうすればこの問題を回避できるのでしょうか?

—

4. 精度問題を華麗に回避するベストプラクティス

実務で安全に小数を扱うためには、主に2つのアプローチがあります。

方法A:表示用なら `toStringAsFixed()` を使う

ユーザー画面に「結果を見せるだけ」であれば、指定した小数点以下の桁数で四捨五入して文字列化するメソッドが使えます。

void main() {
double a = 0.1;
double b = 0.2;
double result = a + b;

// 小数点第2位までで丸めて文字列にする
print(result.toStringAsFixed(2)); // 出力: “0.30”
}

※注意:`toStringAsFixed` は戻り値が String(文字列)になるため、そのまま次の計算に使うのには向いていません。あくまでUI表示用と割り切りましょう。

方法B:実務の王道!「整数(int)」に変換して計算する(推奨)

金融計算や厳密な小数計算の現場では、「あらかじめ100倍や1000倍して `int`(整数)の状態で計算し、最後に割る」というテクニックが黄金律として使われます。

整数であれば、コンピュータは誤差なく完璧に計算できます。

void main() {
// 例: 125.50円 と 300.25円 の計算をしたい場合
// 100を掛けて「銭(セン)」や「厘」単位の整数にしてしまう!
int price1Cents = 12550; // 125.50 100
int price2Cents = 30025; // 300.25 100

int totalCents = price1Cents + price2Cents; // 42575

// 表示する時だけ double に戻して 100 で割る
double finalAmount = totalCents / 100;

print(finalAmount); // 出力: 425.75 (完全な精度を維持!)
}

これなら、2進数の魔力に邪魔されることなく、1円の狂いもない正確な計算が担保できます。

—

まとめ

  • `num` は抽象型なので、基本は具象型である `int` や `double` を明示して使おう。
  • `double` は2進数で小数を扱う都合上、必ず微小な丸め誤差(例: 0.3000004)が発生する。
  • 金額や厳密さが求められる計算では、あらかじめ整数(`int`)にスケールアップして計算するのがプロの安全策。

Dartの言語仕様とコンパイラ、そして内部のメモリの動きまでを少し意識するだけで、あなたの書くコードの信頼性は劇的に跳ね上がります。

この基本をしっかり押さえて、堅牢で美しいDart/Flutterアプリケーションを一緒に作り上げていきましょう!

タイトルとURLをコピーしました