通貨計算の罠:Doubleがもたらす破滅と、Currency・Decimalによる極限の精度制御
Excel VBAにおける数値計算の根幹を揺るがす事実から始めよう。
あなたが何気なく使っている `Double` 型は、金融システムや厳密な集計処理においては「毒」である。
IEEE 754倍精度浮動小数点数。これが `Double` の正体だ。
CPUのハードウェア演算器に直結しており、速度面では最速だが、「10進数の小数を2進数で厳密に表現できない」という致命的な宿命を背負っている。
例えば、`0.1 + 0.2` を `Double` で計算したとき、その結果が `0.3` にならないことは、プログラマの間では古典的なジョークだ。しかし、これが数千行におよぶ財務諸表の突合や、APIを介した決済システムで発生した瞬間、それはジョークではなく「監査法人の指摘事項」または「システム障害」へと変貌する。
本稿では、レガシーなVBAアーキテクチャの最前線において、浮動小数点誤差を完全に駆逐し、通貨計算の精度を担保するための極限の知見を授ける。
—
1. なぜ `Double` 型は金融計算で使ってはいけないのか
VBAの既定のデータ型、あるいは深く考えずに採用される `Double` は、8バイト(64ビット)のメモリを使用し、符号、指数部、仮数部を保持する。
[符号: 1bit] [指数部: 11bit] [仮数部: 52bit]
この構造が生む最大の弊害は 「丸め誤差(Rounding Error)」の蓄積 である。
数百万件のトランザクションをループ処理し、1円未満の端数を切り捨て・切り上げするような処理において、`Double` を使った瞬間に計算結果は「確率的なもの」になる。
「小数点以下第4位までだから大丈夫だ」という甘い認識は捨てろ。
誤差は蓄積する。1回の演算では `0.000000000001` のズレであっても、累算処理のなかで雪だるま式に膨れ上がり、最終的な締日処理で致命的な金額の不一致を引き起こす。
金融・財務系ツールにおいて、数値の正確性は「正義」ではなく「絶対要件」である。この要件を満たすためにVBAプログラマが手にするべき武器が、`Currency` 型と `Decimal` 型だ。
—
2. 最適解の選択:`Currency` 型 vs `Decimal` 型
VBA(および基盤となるCOMオートメーション)において、正確な10進数演算を行うための選択肢は実質的に2つ存在する。それぞれの特性をメモリレイアウトとパフォーマンスの観点から見極めよう。
A. `Currency` 型(推奨:速度と精度のバランスの極み)
- 内部構造: 8バイトの整数(Scaled Integer)
- 有効範囲: -922,337,203,685,477.5808 ~ 922,337,203,685,477.5807
- 特徴: 小数点以下固定で4桁の精度を持つ。実体は「10,000倍された整数」としてCPUで処理されるため、浮動小数点演算プロセッサ(FPU)ではなく整数演算として高速に処理される。
B. `Decimal` 型(極限の精度:Variantの奥底に潜む巨像)
- 内部構造: 14バイト(Variantのデータ型内部でのみ生存可能)
- 有効範囲: 小数点以下28桁までの絶対精度
- 特徴: 純粋な10進浮動小数点数。極めて高い精度を誇るが、`Dim x As Decimal` という直接宣言がVBA文法上では許可されておらず、`CDec()` 関数を経由して `Variant` 型として保持・演算する必要がある。そのため、オーバーヘッドが大きく、パフォーマンスは `Currency` に劣る。
【型選択のアーキテクチャ指針】
1. 通常の実務・財務・決済・税計算: 迷わず `Currency` 型 を採用せよ。小数の桁数が4桁で足りる(日本円や一般的な外貨の最小単位を含む)限り、速度と精度の両面で最適解となる。
2. 超高精度な金利計算・暗号通貨の微小単位・複雑な比率配分: 小数点以下5桁以上の精度が強制される場合のみ、`Decimal` 型(Variant経由) を投入する。
—
3. 実践:誤差を完全に排除するVBA実装パターン
ここでは、実際の業務システムを想定し、`Double` が引き起こす破滅と、`Currency` / `Decimal` による正しい実装を比較する。
悪例:`Double` 型による消費税・総額計算(破綻するコード)
以下のコードを実行してみよ。意図しない端数処理のバグを生む典型例だ。
Sub DangerousCalculation_Double()
Dim subtotal As Double
Dim taxRate As Double
Dim total As Double
subtotal = 1234567.89
taxRate = 0.1
‘ Double同士の乗算は誤差を生む
total = subtotal (1 + taxRate)
Debug.Print “Double Result: ” & total
‘ 期待値とわずかにズレた値が出力されるリスクがある
End Sub
正戒:`Currency` 型による堅牢な実装
数値を代入・演算する段階から一貫して `Currency` 型(または明示的なキャスト)を使用する。
Sub SafeCalculation_Currency()
‘ すべての変数をCurrencyで定義
Dim subtotal As Currency
Dim taxRate As Currency
Dim total As Currency
subtotal = 1234567.89
taxRate = 0.1
‘ 整数スケーリングされた高速な演算が行われる
total = subtotal (CCur(1) + taxRate)
Debug.Print “Currency Result (Exact): ” & total
End Sub
極限:`Decimal` 型(Variant)を用いた28桁精度の強制
もし小数点以下第5位以降の精度が必要な場合、以下のように `CDec` と `Variant` を駆使してメモリ上に10進数コンテキストを構築する。
Sub ExtremeCalculation_Decimal()
Dim v1 As Variant
Dim v2 As Variant
Dim vResult As Variant
‘ CDec関数により、Variant内部にDecimal型(14バイト)を強制格納
v1 = CDec(“1234567890.123456789”)
v2 = CDec(“0.000000001”)
vResult = v1 + v2
‘ 型の確認(TypeNameが “Decimal” を返すことを確認)
Debug.Print “Type: ” & TypeName(vResult)
Debug.Print “Decimal Result: ” & CStr(vResult)
End Sub
—
4. システム間連携・APIデータ処理における注意点
現代のVBAシステムは、孤立したデスクトップツールではない。REST APIを叩き、JSONやXMLをパースし、データベースと同期する「ハブ」として機能する。
ここで発生するのが 「型マッピングの罠」 だ。
1. JSONパーサーの挙動:
多くのVBA製JSONパーサー(VBA-JSONなど)は、数値を自動的に `Double` または `Variant(Double)` としてメモリに展開する。APIから取得した金額データをそのまま `Double` として受け取った時点で、すでに誤差が混入している。
2. 対策:
APIから取得した文字列(JSONの `”12345.67″` など)は、数値に変換する前にパースし、必ず `CCur()` または `CDec()` を通して受領すること。暗黙の型変換(Coercion)に頼る設計は、シニアエンジニアの恥と知れ。
‘ APIレスポンス(文字列)からCurrencyへの安全なコンバージョン
Function ParseCurrencyFromAPI(ByVal jsonValue As String) As Currency
If IsNumeric(jsonValue) Then
‘ 文字列を直接Currencyにキャストして誤差の侵入を防ぐ
ParseCurrencyFromAPI = CCur(jsonValue)
Else
ParseCurrencyFromAPI = 0@ ‘ Currencyリテラル(@サフィックス)
End If
End Function
—
5. チーフアーキテクトからの提言
VBAという言語は、その歴史の長さゆえに「適当に書いても動く」という魔力を持っている。しかし、企業の基幹データを扱う業務システムにおいて、プログラマの「動けばいい」という妥協は、やがて組織に取り返しのつかない損失をもたらす。
- 画面上の見た目(セルの表示形式)で小数を丸めているからといって、内部のメモリ上で `Double` を泳がせてはならない。
- 計算の起因となる変数の宣言から、関数の戻り値、APIとのインターフェースに至るまで、すべてのレイヤーで 「10進数精度の担保」 を意識せよ。
型を制する者は、VBAを制する。
今日からあなたのコードベースにおけるすべての金融計算を `Currency` および `Decimal` で武装し、浮動小数点誤差という亡霊を完全に駆逐せよ。
