こんにちは! 業務システムの裏側を支えるエンジニアの世界へようこそ。
Excelのマクロ記録から一歩踏み出し、本格的なシステム開発の世界に足を踏み入れたあなたへ、今日はとても大切な「数字の扱い方」のお話をします。
システム開発の現場で、避けて通れないのが「数値の丸め処理(四捨五入・切り捨て・切り上げ)」です。
「なんだ、`Math.Round`を使えば一発じゃないか」と思ったそこのあなた。実はそこには、コンピュータサイエンスの深くて暗い、しかし知っていれば怖くない「浮動小数点数の罠」が潜んでいるのです。
ここをクリアすれば、あなたのVB.NETの基本スキルはワンランク上のプロ仕様になります。さあ、一緒に紐解いていきましょう!
—
1. 誰もがハマる?「2.5」の四捨五入が「2」になる怪現象
まずは、以下のコードを見てください。あなたなら、これが実行されたときにコンソールに何が表示されると予想しますか?
.net
Module Module1
Sub Main()
‘ 2.5を四捨五入してみる
Dim val As Double = 2.5
Dim result As Double = Math.Round(val)
Console.WriteLine(result)
End Sub
End Module
「もちろん『3』に決まってるじゃないですか、先輩!」
……そう思いますよね。私も最初はそう信じて疑いませんでした。しかし、このコードを実行すると、なんと画面には 「2」 が表示されます。
「えっ、バグ!? VB.NETが壊れたの!?」
いいえ、壊れていません。これは仕様です。そして、世界中のプログラマが一度はハマる「丸め誤差」の入口なのです。
—
2. なぜ「四捨五入」で「2」になるのか?(JIS丸めの正体)
VB.NETの `Math.Round` メソッドは、デフォルトでは学校で習った「四捨五入(JIS B 8601等に基づく偶数丸め / 銀行家ズ・ロウン:Banker’s Rounding)」を採用しています。
- 四捨五入(学校方式): 5以上は切り上げ、4以下は切り捨て。
- 偶数丸め(JIS方式): 「5」の正確な真ん中の数字に出会ったとき、「最も近い偶数」の方へ丸める。
なぜこんな面倒なことをするのでしょうか?
それは、大量のデータを足し合わせる業務システム(金融や会計など)において、常に「切り上げ」や「四捨五入」を偏って行うと、累積したときに実際の数値よりも全体が大きくなりすぎてしまう(バイアスがかかる)からです。
「5」のときだけ、切り上げと切り捨てを五分五分(偶数方向)に振り分けることで、誤差の蓄積を防ぐという、先人たちの知恵なんですね。
しかし、日本の一般的なビジネス慣習では「学校方式の四捨五入をしてほしい!」という要望が圧倒的多数です。その場合は、以下のように第2引数(または第3引数)を追加します。
.net
‘ 私たちが普段期待する「四捨五入(MidpointRounding.AwayFromZero)」を指定する
Dim result As Double = Math.Round(2.5, 0, MidpointRounding.AwayFromZero)
Console.WriteLine(result) ‘ これで無事に「3」になります!
—
3. もう一つの悪夢「浮動小数点数の誤差」
さて、JIS丸めの謎をクリアしたあなたに、さらに重要な現実をお伝えします。
コンピュータは、人間が使う「10進数(0.1や0.2)」を、内部では「2進数(0と1の組み合わせ)」で表現しています。これが原因で、「目に見えている数値と、コンピュータが持っている数値が微妙に違う」という現象が起きます。
例えば、有名な「0.1 + 0.2」の問題です。
.net
Dim a As Double = 0.1
Dim b As Double = 0.2
Dim c As Double = a + b
Console.WriteLine(c) ‘ 何が表示されるでしょうか?
画面に出るのは `0.3`……ではなく、なんと `0.30000000000000004` です!
わずかですが、後ろにゴミのような数値がくっついていますよね。
この状態で「切り捨て(`Math.Floor`)」などを実行するとどうなるでしょう?
本来なら「0.3」に対して切り捨て処理をしたかったのに、コンピュータにとっては「0.30000000000000004」なので、予期せぬ計算結果を生む原因になります。これが浮動小数点数の罠です。
—
4. 業務システムで絶対に守るべき「3つの鉄則」
初学者のあなたが、現場でエラーや不具合を出さないための「丸め処理の極意」を授けます。この3つを覚えておけばバッチリです。
鉄則その1:金額や単価には `Double` ではなく `Decimal` を使え!
科学技術計算などでは `Double`(倍精度浮動小数点数)が必須ですが、「1円の狂いも許されない業務システム(お金の計算)」では、必ず `Decimal` 型を使いましょう。
`Decimal` 型は10進数をそのまま内部で保持できるため、先ほどの「0.1 + 0.2 = 0.30000000…」のような誤差が発生しません。
.net
‘ 金額を扱うときは必ず Decimal を使う(末尾に D をつけるか、CTypeを使う)
Dim price As Decimal = 100.5D
Dim tax As Decimal = price 1.1D
鉄則その2:四捨五入は `MidpointRounding.AwayFromZero` を明示せよ!
先ほど解説した通り、通常の四捨五入をしたい場合は、省略せずに `MidpointRounding.AwayFromZero` を指定する癖をつけましょう。
鉄則その3:切り捨て・切り上げの基本を押さえる
VB.NETでの切り捨て(`Math.Floor` / `Math.Truncate`)と切り上げ(`Math.Ceiling`)の挙動も整理しておきます。
.net
‘ — 切り捨て (Math.Floor vs Math.Truncate) —
‘ Math.Floor: より小さい整数に向かって切り捨てる
Console.WriteLine(Math.Floor(2.9)) ‘ 結果: 2
Console.WriteLine(Math.Floor(-2.9)) ‘ 結果: -3 (マイナス方向は小さくなるので注意!)
‘ Math.Truncate: 単純に小数点を切り捨てる(ゼロに向かう)
Console.WriteLine(Math.Truncate(2.9)) ‘ 結果: 2
Console.WriteLine(Math.Truncate(-2.9)) ‘ 結果: -2
‘ — 切り上げ (Math.Ceiling) —
Console.WriteLine(Math.Ceiling(2.1)) ‘ 結果: 3
Console.WriteLine(Math.Ceiling(-2.1)) ‘ 結果: -2
マイナスを扱うときの `Math.Floor` の動き(-2.9 が -3 になる)は、初学者がよくハマるポイントなのでメモしておいてくださいね。
—
まとめ:正しい知識でコードに自信を持とう
今回は、VB.NETにおけるMathクラスの活用術と、数値の丸め誤差について解説しました。
- `Math.Round` はデフォルトでは偶数丸め(JIS丸め)になる!
- 通常の四捨五入をしたいなら `MidpointRounding.AwayFromZero` を添える!
- お金や正確な数値を扱うときは `Double` ではなく `Decimal` を選ぶ!
「動くけれど、なぜそうなるのか分からない」という状態から、「裏側の仕組みを知った上でコントロールできている」という状態へ。ここまで理解できたあなたは、もうただの初心者ではありません立派なエンジニアの第一歩を踏み出しています。
ここをクリアできれば、VB.NETの基本はもうバッチリです!
自信を持って、次の実装へ進みましょう!
