VBAの小数以下の演算誤差について
ワークシートで時間計算をしていると、時に不可解な結果に見舞われることがあります。
エクセルの時間は、1日を1とした数値で管理されているため、1時間は1の24分の1の数値になります。
この小数以下の計算において、エクセルが正しく計算できないというものです。
エクセルに限らず、一般的にコンピューターでは小数の計算を正しく行う事が出来ません。
今回は、VBAで小数以下を扱ったときに発生する演算誤差について、どのようなものかの紹介と解決方法を解説します。
小数以下の演算誤差とは
2進数は、桁の数値が2になると桁上がりするものです。
つまり、1桁上がるごとに2倍されていきます。
では、小数以下はどうなるかと言うと、1桁下がるごとに1/2になっていきます。
10進数 | 2進数 |
256 | 100000000 |
128 | 10000000 |
64 | 1000000 |
32 | 100000 |
16 | 10000 |
8 | 1000 |
4 | 100 |
2 | 10 |
1 | 1 |
0.5 | 0.1 |
0.25 | 0.01 |
0.125 | 0.001 |
0.0625 | 0.0001 |
0.03125 | 0.00001 |
0.015625 | 0.000001 |
0.0078125 | 0.0000001 |
0.00390625 | 0.00000001 |
0.001953125 | 0.000000001 |
この表をみるとなんとなくわかるかと思いますが、表の中間の10進数値を2進数で正確に表すことはできません。
例えば、10進数の0.1は、0.00011001100110011・・・このような2進数になってしまいます。
データ型としては、Single(単精度浮動小数点数型)とDouble(倍精度浮動小数点数型)が存在します。
変数宣言のデータ型については、以下を参照してください。
しかし、0.1のような数値は正確な小数として表現できません、どこかで桁落ちしてしまいます。
これにより、正確な小数を扱えていないのでVBAでは誤差が発生してしまいます。
?0.1*3=0.3
これは
False
と出力されます。
この他、小数の四則演算を適当な数値でいろいろ確認してみてください。
TrueになったりFalseになるのが確認できると思います。
小数以下の演算誤差を実際のVBAで確認
Sub test1()
Debug.Print 0.1 + 0.2 = 0.3
Debug.Print 0.2 + 0.3 = 0.5
Debug.Print 0.3 + 0.4 = 0.7
Debug.Print 0.4 + 0.5 = 0.9
Debug.Print 0.5 + 0.6 = 1.1
Debug.Print 0.6 + 0.7 = 1.3
Debug.Print 0.7 + 0.8 = 1.5
Debug.Print 0.8 + 0.9 = 1.7
Debug.Print 0.9 + 1 = 1.9
End Sub
これを実行すると、イミディエイト ウインドウに、
True
True
True
True
False
True
False
True
これですと確認しづらいですし、VBAをいろいろ変更したいので以下のようなVBAで話を進めます。
Sub test2()
Dim i As Long
Dim i1
Dim i2
Cells.Clear
For i1 = 0.1 To 0.9 Step 0.1
i2 = i1 + 0.1
i = i + 1
Cells(i, 1) = i1
Cells(i, 2) = i2
Cells(i, 3) = (i1 + i2 = i1 * 2 + 0.1)
Next
End Sub
実行すると、アクティブシートに以下のように出力されます。
0.1 | 0.2 | TRUE |
0.2 | 0.3 | TRUE |
0.3 | 0.4 | TRUE |
0.4 | 0.5 | TRUE |
0.5 | 0.6 | TRUE |
0.6 | 0.7 | FALSE |
0.7 | 0.8 | TRUE |
0.8 | 0.9 | FALSE |
0.9 | 1 | TRUE |
データ型で小数以下の演算誤差に対応
このVBAにデータ型を指定して演算誤差に対応していこうという趣旨になります。
VBAでは、何も指定せずに小数を記述した場合はDoubleになります。
したがって、30000 * 2 はオーバーフローとなります。
このような場合は、30000& * 2 このようにLongの型文字&を付けるかCLng関数を使ってください。
Double(倍精度浮動小数点数型)
Sub test2()
Dim i As Long
Dim i1
Dim i2
Cells.Clear
For i1 = 0.1 To 0.9 Step 0.1
i2 = i1 + 0.1
i = i + 1
Cells(i, 1) = i1
Cells(i, 2) = i2
Cells(i, 3) = (i1 + i2 = i1 * 2 + 0.1)
Next
End Sub
結果は先と同様に、
0.1 | 0.2 | TRUE |
0.2 | 0.3 | TRUE |
0.3 | 0.4 | TRUE |
0.4 | 0.5 | TRUE |
0.5 | 0.6 | TRUE |
0.6 | 0.7 | FALSE |
0.7 | 0.8 | TRUE |
0.8 | 0.9 | FALSE |
0.9 | 1 | TRUE |
Single(単精度浮動小数点数型)
Sub test4()
Dim i As Long
Dim i1 As Single
Dim i2 As Single
Cells.Clear
For i1 = 0.1 To 0.9 Step 0.1
i2 = i1 + 0.1
i = i + 1
Cells(i, 1) = i1
Cells(i, 2) = i2
Cells(i, 3) = (i1 + i2 = i1 * 2 + 0.1)
Next
End Sub
実行結果は、
0.100000001 | 0.200000003 | TRUE |
0.200000003 | 0.300000012 | TRUE |
0.300000012 | 0.400000006 | TRUE |
0.400000006 | 0.5 | FALSE |
0.5 | 0.600000024 | TRUE |
0.600000024 | 0.700000048 | TRUE |
0.700000048 | 0.800000072 | TRUE |
0.800000072 | 0.900000095 | TRUE |
あれれ、と言う感じですよね。
そもそも、0.1そのものが正しくセルに入っていません。
しかも、最後の1行が出力されていません。
Forで0.9までにしているので、最後が0.9をわずかに超えてしまっているという事です。
0.1!
全ての数値をこのようにしても結果は同じです。
しかし、上記の通り演算誤差が完全になくなるわけではありません。
Singleを使えば演算誤差が出ないといった乱暴な意見を見かけることもありますが決してそのようなことはありません。
?0.4!-0.3!
イミディエイトで簡単に確認できます。
Currency(通貨型)
10,000でスケーリングされて、小数点の左側に15桁、右側に4桁の固定小数点数です。
Sub test5()
Dim i As Long
Dim i1 As Currency
Dim i2 As Currency
Cells.Clear
For i1 = 0.1 To 0.9 Step 0.1
i2 = i1 + 0.1
i = i + 1
Cells(i, 1) = i1
Cells(i, 2) = i2
Cells(i, 3) = (i1 + i2 = i1 * 2 + 0.1)
Next
End Sub
\0.10 | \0.20 | TRUE |
\0.20 | \0.30 | TRUE |
\0.30 | \0.40 | TRUE |
\0.40 | \0.50 | TRUE |
\0.50 | \0.60 | TRUE |
\0.60 | \0.70 | TRUE |
\0.70 | \0.80 | TRUE |
\0.80 | \0.90 | TRUE |
\0.90 | \1.00 | TRUE |
ご覧の通り、通貨型なので書式設定されていないセルに出力すると通貨記号が付いてしまいますが、演算誤差は発生しません。
小数が4桁までなら演算誤差を気にする必要がありません。
Decimal(10進型)
Decimalを扱うには、CDec関数 を使用します。
変数に入れる場合は、変数の型はVariantで宣言します。
Sub test6()
Dim i As Long
Dim i1
Dim i2
Cells.Clear
For i1 = CDec(0.1) To CDec(0.9) Step CDec(0.1)
i2 = i1 + 0.1
i = i + 1
Cells(i, 1) = i1
Cells(i, 2) = i2
Cells(i, 3) = (i1 + i2 = i1 * 2 + 0.1)
Next
End Sub
実行結果は、
0.1 | 0.2 | TRUE |
0.2 | 0.3 | TRUE |
0.3 | 0.4 | TRUE |
0.4 | 0.5 | TRUE |
0.5 | 0.6 | TRUE |
0.6 | 0.7 | TRUE |
0.7 | 0.8 | TRUE |
0.8 | 0.9 | TRUE |
0.9 | 1 | TRUE |
何も問題ありません。
Decimalが扱える桁数範囲内であれば演算誤差を気にする必要はありません。
Decimalのデータ範囲については、CDec関数をを参照してください。
小数以下の演算誤差にデータ型以外で対応する
整数に直して計算
Sub test7()
Dim i As Long
Dim i1 As Long
Dim i2 As Long
Cells.Clear
For i1 = 1 To 9 Step 1
i2 = i1 + 1
i = i + 1
Cells(i, 1) = i1 / 100000
Cells(i, 2) = i2 / 100000
Cells(i, 3) = ((i1 + i2) / 100000 = (i1 * 2 + 1) / 100000)
Next
End Sub
実行結果は、
0.00001 | 0.00002 | TRUE |
0.00002 | 0.00003 | TRUE |
0.00003 | 0.00004 | TRUE |
0.00004 | 0.00005 | TRUE |
0.00005 | 0.00006 | TRUE |
0.00006 | 0.00007 | TRUE |
0.00007 | 0.00008 | TRUE |
0.00008 | 0.00009 | TRUE |
0.00009 | 0.0001 | TRUE |
ここでは小数5桁なので100000倍して100000で割っています。
この結果は当然問題ありません。
ただし、小数桁数が多いと整数がLongに入りきれなくなる場合が出てきます。
64bitエクセルならLongLongを使えば良いでしょう。
これは結果として、Currencyの内部処理を自力でやっていることと同じだと考えれば良いでしょう。
有効桁数で丸める
小数の桁数が決まっているのなら、その桁数に丸めてしまえば良いのです。
演算誤差は、ずっと小さい小数で発生しているので、必要な桁数に丸めてしまえば誤差は無くなります。
Sub test8()
Dim i As Long
Dim i1 As Double
Dim i2 As Double
Cells.Clear
For i1 = 0.00001 To 0.00009 Step 0.00001
i2 = Round(i1 + 0.00001, 5)
i = i + 1
Cells(i, 1) = i1
Cells(i, 2) = i2
Cells(i, 3) = (Round(i1 + i2, 5) = Round(i1 * 2 + 0.00001, 5))
Next
End Sub
実行結果は、
0.00001 | 0.00002 | TRUE |
0.00002 | 0.00003 | TRUE |
0.00003 | 0.00004 | TRUE |
0.00004 | 0.00005 | TRUE |
0.00005 | 0.00006 | TRUE |
0.00006 | 0.00007 | TRUE |
0.00007 | 0.00008 | TRUE |
0.00008 | 0.00009 | TRUE |
0.00009 | 0.0001 | TRUE |
小数の桁数は、Roundの第二引数で調整できます。
実際に使う場合は、Roundする小数桁数を必要な桁数より少し多めに指定すると良いでしょう。
VBAの小数以下の演算誤差の最後に
小数以下4桁までならCurrencyが簡単かつ確実だと言えるでしょう。
Decimalはより精度が高くなりますが、型宣言できないため扱いづらい面があります。
整数にしたりRoundする方法は、より柔軟ではありますがVBAの記述が面倒になります。
したがって、適宜使い分けると良いでしょう。
VBAでは、税込算出/税抜算出において(余程特種な数式ではない限り)演算誤差が出ることは無いはずです。
同じテーマ「マクロVBA技術解説」の記事
手動計算時の注意点と再計算方法
VBAの用語について:ステートメントとは
オブジェクト変数とは何か
VBAの小数以下の演算誤差について
スピルでVBAの何が変わったか
CharactersプロパティとCharactersオブジェクト
ユーザーに絶対に停止させたくない場合のVBA設定
印刷範囲の設定・印刷範囲のクリア
VBAの省略可能な記述について
VBAのVariant型について
VBAのインデントについて
新着記事NEW ・・・新着記事一覧を見る
WshNetwork(ネットワークドライブの割り当て等)|VBA技術解説(2025-04-09)
TRANSLATE関数(翻訳) DETECTLANGUAGE関数(言語識別)|エクセル入門(2025-04-08)
QRコード、バーコード作成の覚え書き|エクセル関数応用(2025-04-05)
TRIMRANGE関数(セル範囲をトリム:端の空白セルを除外)|エクセル入門(2024-08-30)
正規表現関数(REGEXTEST,REGEXREPLACE,REGEXEXTRACT)|エクセル入門(2024-07-02)
エクセルが起動しない、Excelが立ち上がらない|エクセル雑感(2024-04-11)
ブール型(Boolean)のis変数・フラグについて|VBA技術解説(2024-04-05)
テキストの内容によって図形を削除する|VBA技術解説(2024-04-02)
ExcelマクロVBA入門目次|エクセルの神髄(2024-03-20)
VBA10大躓きポイント(初心者が躓きやすいポイント)|VBA技術解説(2024-03-05)
アクセスランキング ・・・ ランキング一覧を見る
1.最終行の取得(End,Rows.Count)|VBA入門
2.セルのコピー&値の貼り付け(PasteSpecial)|VBA入門
3.繰り返し処理(For Next)|VBA入門
4.変数宣言のDimとデータ型|VBA入門
5.RangeとCellsの使い方|VBA入門
6.ひらがな⇔カタカナの変換|エクセル基本操作
7.セルのクリア(Clear,ClearContents)|VBA入門
8.メッセージボックス(MsgBox関数)|VBA入門
9.ブックを閉じる・保存(Close,Save,SaveAs)|VBA入門
10.条件分岐(Select Case)|VBA入門
- ホーム
- マクロVBA応用編
- マクロVBA技術解説
- VBAの小数以下の演算誤差について
このサイトがお役に立ちましたら「シェア」「Bookmark」をお願いいたします。
記述には細心の注意をしたつもりですが、
間違いやご指摘がありましたら、「お問い合わせ」からお知らせいただけると幸いです。
掲載のVBAコードは動作を保証するものではなく、あくまでVBA学習のサンプルとして掲載しています。
掲載のVBAコードは自己責任でご使用ください。万一データ破損等の損害が発生しても責任は負いません。