#Excelはセルの中の計算でも演算誤差が発生する
【悲報】“達人”芳坂和行氏に学ぶ、エクセル「演算誤差」対策講座がPC Onlineから消えていた
ここで取り扱った一番簡単な演算誤差の発生方法は
=1.2-1.1
でした。
マイクロソフトも以前から指摘受け、ちょくちょく記事を出していましたが、下記記事のURLを検索すると2010年ごろからHITするので、2010年ごろについに正式に演算誤差があること、Excel97で改善したことを記事にしています。
#演算誤差を調べる理由 消費税10%
消費税の正式な計算方法は、X×1.1です。総額表示ですから。この1.1が2進で307桁周期の循環小数となります。つまり演算誤差を抱え込む計算です。このため、何らかの方法であらかじめ演算誤差が発生することを回避する必要があります。
結論から言うと
- 結論から言うとVBAでは`1.1@`とすることで必ず回避できます。また単純に10%を乗じる場合でも`0.1@`(または`0.1!`)とすることで回避できます。VBAで覚えるべきなのは長整数、単精度浮動小数点、通貨の3つです。余力があればCDecを覚えてください。
- 上記のような計算であれば
=1.2!-1.1!
関数では=CSng(1.2)-CSng(1.1)
とすると回避できます。まず1.1!ビックリマークをつけましょう。 - このように数字の後ろにつく文字をリテラル文字(Literal characters(といいます。これはついた数のデータ型を規定します。変数の宣言に似ています。また関数ではClng,CDec,CSng,CCurと同じ効果があります。変数の後ろにつけることもできます。DimでつけるとAsの代わりになります。ただし、`Dim i As Long` `Dim i&` 可読性にかけるのが難点です。
- [1より小さい数を含む二進数表現 - 聖愛中学高等学校](https://seiai.ed.jp/sys/text/csc/ch12/c12a300.html)をみると0.5以外は全滅です。(このため四捨五入の計算の0.5はリテラル強制が不要なことがわかります。)
- 整数は長整数型Long & Clngを使いましょう。ByteはBinary用であり、IntegerはLongに変換されて計算が早くなりません。むしろ遅いです。
- 利子、利息には単精度浮動小数点型
Single
!
CSng()
を使いましょう。6桁までは正確なので、簡単な小数点の計算、利息、および `For i! = 0 to 0.0001! Step 0.000001!` のような微小値でのStep数に役立ちます。すごく地味で倍精度のほうが強くてカッコよい語感ですが、地味に非常に実務で使うのはこの単精度浮動小数点型です。 - 日常ではSingleは十分な精度ですが、科学技術計算には精度が低いです。たとえばもっと小さい微小値のFor NextはCDecを使う必要があります。
- 十進型の代用になるのは通貨型 Currency @ CCurです。ただし小数点4桁で固定されます。なので日本円では長整数では計算できない大きな数に向いています。
- 十進型はVariant扱いです。Visual Basicでは @ は十進型のリテラル文字ですが、Visual Basicは通貨型になります。返る値はVariantです。CDecは一つ一つ数字や変数を囲む必要があります `=CDec(1.2)-CDec(1.1)`
- なにもしないと倍精度浮動小数点型Double # CDblになります。300桁の数字を扱える(ただし`*10^x`表示)演算誤差が発生しやすいデータ型です。これは全く覚える必要がありません。なにもしないとそうなるからです。
- VBAのRound関数はAccessのRound関数と同じで銀行まるめであり、ExcelのWorksheetFunction.Roundと違います。これには[xlRnd関数](https://qiita.com/Q11Q/items/ad8e3139b02f495db181)というのを作ってあります。
- 変数の型の名称は各言語ごとに違います。SingleはFloatと言われたり、SingleがDoubleの意味を持ったりします。PythonやCを扱われている方はご注意ください。
Sub test()
Dim DT As Single: DT = Timer
Dim i As Long
For i = 1 To 100
Debug.Print 1.2 - 1.1
Next i
Debug.Print "Time(Single)",Timer - DT
End Sub
Excelと同じようにやってみました。
##やはり演算誤差が発生する
9.99999999999999E-02
9.99999999999999E-02
Time(Single) 0.328125
やはり0.1になっていません。
##解決方法はリテラル文字?
VBA Accessを始めるまえに簡単にパパッと作る関数xlRnd2にさらにLOG10とStaticと変数の宣言
Docs.NETVisual Basic のガイド言語機能定数と列挙体/定数とリテラルのデータ型 (Visual Basic)
次の表は、それを囲む文字と Visual Basic で使用可能な型宣言文字。
囲み文字とはたとえば日付で #12/21/2019#
文字だと "Hello! World!"
と囲むことで、日付型、文字列型と認識するということです。
データの種類 | 囲み文字 | 末尾の型文字 |
---|---|---|
Boolean | (なし) | (なし) |
Byte | (なし) | (なし) |
Char | " | C |
Date | # | (なし) |
Decimal | (なし) | D または@ |
Double | (なし) | R または # |
Integer | (なし) | $I または % |
Long | (なし) | L または & |
Short | (なし) | S |
Single | (なし) | F または! |
String | " | (なし) |
LongLong | (なし) | キャレット(^) |
ところが
Office VBAリファレンス>VBA言語リファレンス>通貨データ型
通貨変数は、整数形式で 64 ビット (8 ビット) 数として格納されます。小数点の左 (整数部分) は 15 桁、小数点以下は 4 桁で、10,000 単位で調整されます。表示範囲は -922,337,203,685,477.5808 ~ 922,337,203,685,477.5807 です。 Currency の 型宣言文字はアットマーク ( @ ) です。
Currencyデータ型は、通貨に関連する計算、および正確性が特に重要な固定小数点計算を行う際に便利です。
浮動小数点数とその精度からすると演算誤差のない事務計算とは固定小数点型とみなされている。こうしたことからCurrencyが推奨されている。
10 の累乗でスケーリングされた 10 進数値を格納するデータ型。ゼロ スケーリングの値つまり小数点のない数値の場合、範囲は +/-79,228,162,514,264,337,593,543,950,335 です。小数点以下 28 桁の数値の場合、範囲は +/-7.9228162514264337593543950335 です。 Decimal として表現できる最も小さい 0 以外の値は 0.0000000000000000000000000001 です。
現時点では、 Decimal データ型は Variant 内でのみ使用できることに注意してください。変数を Decimal 型として宣言することはできません。ただし、 CDec 関数を使用するとサブタイプが Decimal である Variant を作成できます。
データ型の概要
昔は変数のByte数で計算ができなかったりしたので、大変だったのです。4バイトとか小さく見えますが、計算で何度も使ったりしますからね。
データ型 | 記憶領域サイズ | 範囲 | 備考 |
---|---|---|---|
バイト型 (Byte) | 1 バイト | 0 ~ 255 | 本来はBinaryのための型なのでIntegerを使うこと。 |
Integer | 2 バイト | -32,768 〜 32,767 | Office2007以降、Integerは内部でLongに置き換えられている。そのためLongを使うより遅くなる。 |
Long (長整数) | 4 バイト | -2,147, 483,648 〜 2,147, 483,647 | 普通よく使う型。 |
Single (単精度浮動小数点数) | 4 バイト | 負の値の場合は -3.402823E38 〜 -1.401298E-45、正の値の場合は 1.401298E-45 〜 3.402823E38 | 小数点以下にある程度強く、倍精度よりメモリを食わない。有効数字は7桁程度。 |
Double (倍精度浮動小数点数) | 8 バイト | 負の値の場合は -1.79769313486231E308 〜 -4.94065645841247E-324、正の値の場合は 4.94065645841247E-324 〜 1.79769313486232E308 | |
Currency (スケーリングされた整数) | 8 バイト | -922,337,203,685,477.5808 ~ 922,337,203,685,477.5807 | 実は十進型の代理、小数点以下は4桁で固定。変数のサイズは64bit。WinAPIでBIG_Integerの置き換えに使う。 |
日付 | 8 バイト | 100 年 1 月 1 日 ~ 9999 年 12 月 31 日 | Excelの関数で扱う範囲1900年3月1日以降より実は広い。なぜか公式は忘れているが、時刻も入る。00:00:00~23:59:59(.999)。そしてミリセカントまでカウントしているので、時刻型式は999まで入っている。 |
Object | 4 バイト | 任意の Object 参照 | Variantはいい加減すぎるときにとりあえずObjectを使う |
String文字列型(可変長) | 10 バイト + 文字列の長さ | 0 〜 約 20 億 | もちろん20億もいれたら重くて動かない。2GBと説明している例もある。一見数値と関係ないようだが、演算結果が最終的に15桁を超える場合はCstr()で文字列型を強制し、浮動小数点表示ではなく実数表示で返すことができる。 |
String * x 文字列型(固定長) | 文字列の長さ | 1 〜 約 65,400 | 2GBと説明している例もある。可変長も65400くらいが限界じゃないかな。 But As String * 10 のように表現する。この場合全角でも半角でも1文字として数える。全角を2文字でカウントしない。そして宣言しただけでバイト数を食うので、これを多用することはない。 |
Variant型(数値を使用) | 16 バイト | 最大で Double の範囲までの任意の数値 | 実はDoubleでありDecimalまた、本来のDobuleよりバイト数が大きくなっている。 |
Variant型(文字を使用) | 22 バイト + 文字列長 | (64 ビット システムでは 24 バイト) | 実は中でいろいろな型として扱われ過ぎ |
Decimal | 14 バイト | 小数点がない場合は +/-79,228,162,514,264,337,593,543,950,335、 小数点以下が 28桁 の場合は +/-7.9228162514264337593543950335、ゼロではない最小の値は +/-0.0000000000000000000000000001 | 実はVariant型である。Cdecでしか作成できず。型宣言をすることはできない。 |
LongLong (LongLong 整数) | 8 バイト | -9,223,372,036,854,775,808 〜 9,223,372,036,854,775,807 (64 ビット プラットフォームでのみ有効) | 使う機会がない…リテラルはキャレット(^)変数のサイズが64bit |
LongPtr (32 ビット システムでは Long 整数、64 ビット システムでは LongLong 整数) | 32 ビット システムでは 4 バイト、64 ビット システムでは 8 バイト | 32 ビット システムでは -2,147, 483,648 〜 2,147, 483,647、64 ビット システムでは -9,223,372,036,854,775,808 〜 9,223,372,036,854,775,807 | Declare以外では使わない |
注 この表のEは自然対数(e ネーピア数を底とする対数)ではなく基数が10という意味。これは電子計算機業界の慣習。昔からそうなっている。浮動小数点数とその精度
注 すべてのデータ型の配列には、20 バイトのメモリに加えて配列次元ごとに4 バイトおよびデータ自体が占めるバイト数が必要です。データが占めるメモリは、データ要素の数と各要素のサイズを乗算して求めることができます。(略)←というかその計算をするスクリプトがほしい。配列以外はLenBでカウントできる。
注 LongPtr は 32 ビット環境では Long に変換され、64 ビット環境では LongLong に変換されるので、実際のデータ型ではありません。(略)←なら書くな。素人が混乱するじゃないか。
注 配列を含む Variant には、配列だけの場合より 12 バイト余計に必要です。←要素のLenBの総和に12Byte?
注 文字列データの型の間の変換には、 StrConv 関数を使用します。
注 数値を通貨にするときは@、単精度にするときは!を付けます。
固定長と可変長の倍精度Variantの実験
Sub testFixStringLenB()
Dim i As Variant
Dim fxStr As String * 20
Dim varStr As String
Dim buf As String
Dim varSingle As Single
Dim varDbl As Double
fxStr = "あかねさすむらさきのゆきしめのゆきのもりはみずやきみがそでふる"
varStr = fxStr
buf = "あかねさすむらさきのゆきしめのゆきのもり"
Debug.Print LenB(fxStr), LenB(varStr), LenB(buf), fxStr
fxStr = "01234567890abcdefghijklmnopqrstuvwxyz"
varStr = fxStr
buf = ""
buf = "01234567890abcdefghi"
Debug.Print LenB(fxStr), LenB(varStr), LenB(buf), fxStr
For i = 1.623551 To 1.623561 Step 0.000001!
varSingle = CSng(i)
varDbl = CDbl(i)
Debug.Print LenB(i), LenB(varDbl), LenB(varSingle), LenB(CStr(i))
Exit For
Next i
End Sub
40 40 40 あかねさすむらさきのゆきしめのゆきのもり
40 40 40 01234567890abcdefghi
16 8 4 16
固定長が文字列をカットしているが、全角でも半角でも1文字としてカウントしている。
VariantのDoubleはDobule型の2倍である。
というのがここからわかる。本当は可変長は10byte+文字の長さなので50になるはずなのだが…
###ポイントは@はVBAでは通貨型、VB.Netでは十進型となる点
VBAの場合十進型はVariantでしかない。
64Bitのみキャレット(^)は LongLong
F か # Double
D か @ Decimal <DなのにDoubleではないのです
I か % Integer
Lか& Long
F か ! 50.3F 107.5! Single 明示的に小数点2桁をつける
そして
リテラルにつけられた接尾辞のうち、「% integer」、「& Long」、「!」Single、「# Double」、「@ DecimalCurrency」はリテラルだけでなく、変数につけることもできます
とある。
実はなにもつけないとDouble
Debug.Print 1.2 - 1.1#
こういう表記をしようとしても
Debug.Print 1.2 - 1.1
ナンバー記号は消えてしまいます。
つまりVBAでは倍精度が標準で何もしないと倍精度でだったわけです。
##リテラルで十進を強制できるか
###Decimalという型がVBAはあるけど宣言ができない
できればかなり有効ですが、できません。用語集にさりげなく通貨型を推奨とあります。
したがって通貨型を使いましょう。
Debug.Print 1.2D - 1.1
構文エラーになります。
Debug.Print 1.2@ - 1.1@
Debug.Print 1.2! - 1.1!
は成功します。そして答えも0.1になります。
しかし、
Sub Test()
Dim x
x=1.2@
Debug.Print Typename(x)
End Sub
とすると、DecimalではなくCurrencyとなります。
しかし@や!をつけることで正確な計算になりました。
###小数点以下のStepはCdecを使う方が正確
Sub testSingleFoloat()
Dim i As Double
For i = 1.62355551 To 1.62355561 Step CDec(1E-08!)
Debug.Print i, CDbl(i), CSng(i), CCur(i), CDec(i)
Next i
For i = 0.62355551 To 0.62355561 Step CDec(1E-08!)
Debug.Print i, CDbl(i), CSng(i), CCur(i), CDec(i)
Next i
End Sub
最初の1.1.6235551だと単精度は小数点以下が6桁であとが無視される
1.62355551 1.62355551 1.623556 1.6236 1.62355551
1.62355552 1.62355552 1.623556 1.6236 1.62355552
1.62355553 1.62355553 1.623556 1.6236 1.62355553
0.62355551 0.62355551 0.6235555 0.6236 0.62355551
0.62355552 0.62355552 0.6235555 0.6236 0.62355552
0.62355553 0.62355553 0.6235555 0.6236 0.62355553
それではStepを単精度にして小数点6桁を見てみます
Sub testSingleFoloat2()
Dim i As Double
For i = 1.623551 To 1.623561 Step 0.000001!
Debug.Print i, CDbl(i), CSng(i), CCur(i), CDec(i)
Next i
For i = 0.6235551 To 0.623561 Step 0.000001!
Debug.Print i, CDbl(i), CSng(i), CCur(i), CDec(i)
Next i
End Sub
1.623551 1.623551 1.623551 1.6236 1.623551
1.623552 1.623552 1.623552 1.6236 1.623552
1.62355299999999 1.62355299999999 1.623553 1.6236 1.623553
1.62355399999999 1.62355399999999 1.623554 1.6236 1.62355399999999
1.62355499999999 1.62355499999999 1.623555 1.6236 1.62355499999999
略
0.623551 0.623551 0.623551 0.6236 0.623551
0.623551999999997 0.623551999999997 0.623552 0.6236 0.623551999999997
0.623552999999995 0.623552999999995 0.623553 0.6236 0.623552999999995
このように単精度はStep数が正確でなくても食いついてきますので、0<i<1で小数点6桁までは強いです。下記のVSのブログでもFloat(つまり単精度)の有効桁数が7桁としています。6桁までの利率は!をつけて単精度を強制し、それ以下の利率ならCDECで十進を強制し、Step数もCDecで十進を強制しましょう。
##リテラルで染められたコード
Sub test2()
Dim DT!: DT = Timer
Dim i As Long
Dim D1@
D1 = 1.2
For i = 1& To 100&
Debug.Print D1 - 1.1
Next i
Debug.Print "Time(Single)", Timer - DT
End Sub
As Decimal がなくても型宣言で十進通貨型が強制できる
実はそういうことだったのです。
##CDecで囲んでも実は誤差が出る
Sub test()
Dim DT!: DT = Timer
Dim i As Long
Dim D1@
For i = 1& To 100&
Debug.Print CDec(1.2 - 1.1)
Next i
Debug.Print "Time(Single)", Timer - DT
End Sub
これはどうでしょうか
0.0999999999999999
Time(Single) 0.2734375
やはり誤差が出ます。
結局
Cdec(1.2)-Cdec(1.1)
としないとだめなのです。
かっこの中の数字はリテラルがないため倍精度で計算していることを意味しています。
リテラルと型宣言は処理に差があるか
Sub testinteger()
Dim DT!, DT1!, DT2!: DT = Timer
Dim i%, i1%
For i = 1 To 1000
Debug.Print 1.2@ - 1.1@
Next i
DT1 = Timer
For i1 = 1 To 1000
Debug.Print 1.2 - 1.1
Next i1
DT2 = Timer
Debug.Print "Time(Single)", DT2 - DT1, "DT1", DT1 - DT, "差", (DT1 - DT) - (DT2 - DT1)
End Sub
Sub testinteger1()
Dim DT As Single, DT1 As Single, DT2 As Single: DT = Timer
Dim i As Integer, i1 As Integer
For i = 1 To 1000
Debug.Print 1.2@ - 1.1@
Next i
DT1 = Timer
For i1 = 1 To 1000
Debug.Print 1.2 - 1.1
Next i1
DT2 = Timer
Debug.Print "Time(Single)", DT2 - DT1, "DT1", DT1 - DT, "差", (DT1 - DT) - (DT2 - DT1)
End Sub
Sub testLong()
Dim DT!, DT1!, DT2!: DT = Timer
Dim i&, i1&
For i = 1 To 1000
Debug.Print 1.2@ - 1.1@
Next i
DT1 = Timer
For i1 = 1 To 1000
Debug.Print 1.2 - 1.1
Next i1
DT2 = Timer
Debug.Print "Time(Single)", DT2 - DT1, "DT1", DT1 - DT, "差", (DT1 - DT) - (DT2 - DT1)
End Sub
testinteger()
Time(Single) 3.210938 DT1 2.898438 差 -0.3125
Time(Single) 3.023438 DT1 3.015625 差 -0.0078125
Time(Single) 3.25 DT1 2.851563 差-0.3984375
###testinteger2()
Time(Single) 3.132813 DT1 2.914063 差 -0.21875
Time(Single) 2.71875 DT1 3.179688 差 0.4609375
Time(Single) 3.109375 DT1 2.992188 差 -0.1171875
###testLong()
Time(Single) 2.789063 DT1 2.96875 差 0.1796875
Time(Single) 2.664063 DT1 2.898438 差 0.234375
Time(Single) 2.671875 DT1 2.984375 差 0.3125
##少し意外な結果
###DoubleとDecimal Currency ではほとんど差がない、カウンターがLongかIntegerで変わる
カウンターがLongの時はDoubleで計算する方が早い。
###リテラルで宣言するのと型で宣言するのはほとんど差がないがごくわずかに型宣言が早い
型宣言は3.1でリテラルは3.2が出ているので、型宣言がわずかに早いようです。
カウンターはLongの時が早く Integerは遅い
バイト数を使わないIntegerが早いかというとそうではありませんでした。
####IntegerはXPのころから長整数型に変換される分遅くなり、扱える範囲が狭く、処理が遅いデータ型になった
Microsoft Office XP Developer
MicrosoftR Visual BasicR for Applications (VBA) には、整数型 (Integer)、長整数型 (Long)、およびバイト型 (Byte) の 3 種類のデータ型がありますが、いずれも整数を表すことができます。整数型と長整数型は特に使用頻度の高いデータ型です。
整数型と長整数型はいずれも正の数および負の数をとります。この 2 つのデータ型の違いは、数の値の大きさです。整数型の変数は -32,768 〜 32,767 の範囲の値を受け入れるのに対して、長整数型の変数は -2,147,483,648 〜 2,147,483,647 の範囲の値をとります。従来、VBA プログラマは、メモリの消費量が比較的少ない整数を使用してきましたが、最近の VBA のバージョンでは、データ型が整数型と指定されている場合であっても、すべての整数値が長整数型に変換されます。つまり、整数型を指定しても、整数型の変数を使用するパフォーマンス上の利点がなくなったわけです。実際、長整数型の変数を使用すると変換作業が不要なため、効率性が向上する場合があります。
バイト型は 0 〜 255 の範囲に含まれる正の数をとります。バイト型の変数は 1 バイトであり、効率的です。バイト型の変数は、整数値で 255 以下のものをとります。ただし、バイト型は一般的に文字列を対象にした作業で使用されます。文字列での作業では、文字列をバイトの配列に変換するとパフォーマンスが向上する場合があります。
#結果
とりあえずいろいろ変えるとまた差が出るのだろうけど
1. VBAでも演算誤差は発生する。数値は標準では倍精度である。このため小数点が4桁までは@、6桁までは!のリテラル文字をつけると演算誤差がほぼ発生しない。
2.VB.net と異なり@は十進型ではなく通貨型になる。この通貨型は4桁しか小数点を持たない。
3.利率で小数点が4桁を超えて6桁までは単精度浮動小数点型を使用する。
4.CDecは一つ一つ数字や変数を包まないと完全に強制したことにはならない。
5.for i = next のカウンタiは長整数型Longが基本。そしてStep数が小数である場合、Step数にCdecを使う。
6. 型宣言は可読性も考えて、As Longの形で宣言した方がよい。リテラルで宣言するのと速度はほとんど差が出ない。リテラルで宣言するときは可読性に欠け、何の型なのかわかりづらいため、変数名でわかるようにする。
Dim iDbl#, iLong&, iCur@, iSng!, iInt%
Const cnsCurrVal1@ =1999
このように可読性は今一つですね。しかも単精度は階乗と混乱します。
定数と型宣言文字(Const)
ただし、このような使い方をすることはあまりなく、
プログラム内において、リテラル値(定数値)を指定する場合に、
使う事がほとんどです。
###おまけMSDNで演算誤差が扱われているもの
Excelのセルに桁数の多い数字(クレジットカード)を入力すると最後の桁がゼロに変更される
現象
Microsoft Excel では、セルに 15 桁を超える数字を入力すると、15 桁目より下位のすべての桁がゼロに変更されます。たとえば、クレジット カードの ID 番号を次の形式で入力した場合に、この問題が発生します。
####-####-####-####
Excel では、最後の桁がゼロに変更されます。
回避策
この問題を回避するには、セルを文字列として書式設定します。文字列として書式設定すると、セルに 1,024 文字まで表示できます。
詳細
この現象は、計算を目的とした数字、つまり数値として書式設定されたセルでのみ発生します。文字列として書式設定されたセルには 32,767 文字まで入力できます。そのうち 1,024 文字までがワークシートに表示されます。
本来、ユーザー定義の表示形式は数値を使用して機能するように設計されているため、16 桁以上を格納するユーザー定義の表示形式を作成することはできません。たとえば、次の形式を使用して 16 文字のクレジット カード ID を数値として格納することはできません。
####-####-####-####
####-####-####-#### の形式を使用するセルに 1111222233334444 という数字を入力すると、セルに "1111-2222-3333-4440" と表示されます。格納しようとしている実際の数値は 1,111,222,233,334,444 であり、1,000 兆を超えています。しかし、この数値は大きすぎるため、最後の (最下位の) 桁が切り捨てられ、その位置にゼロが入ります。
文字列として書式設定されたセルに数字を入力する場合、すべての文字は入力したとおりに表示されます。これは、クレジット カード ID が数値として格納されず、文字列のまま格納されるためです。
Visual Studio Support Team in Japan2014-10-28 浮動小数点を利用する際に知っておきたいこと
浮動小数点数を使用する際の注意点
浮動小数点数はプログラムで小数を扱うために広く利用されていますが、少し癖のあるデータ型でもあるため、その特性をよく理解して利用しないと、思わぬ落とし穴に遭遇してしまうこともあります。
浮動小数点の特性として、注意した方が良いものは主に以下の 3 点となります。
a) 浮動小数点数の演算に固有の誤差が常に生じる可能性がある。
b) ビルド環境やオプションによって、同一のソースコードでも計算結果が変わる可能性がある。
c) 実行する CPU が変われば、同一の実行モジュールでも計算結果が変わる可能性がある。
(略)
このような特性があるため、厳密な計算が求められる金額計算などに浮動小数点数を利用する際は、特に注意が必要となります。
浮動小数点数の演算結果を完全に一致させることを保証する方法はないため、以下の 2 点に注意して設計・プログラミングを行う必要があります。
方針1 : 浮動小数点数の演算結果については有効桁数の範囲で評価する
方針2 : それでも演算誤差の最小化が必要な場合は 10進形式の型の利用を検討する
小数計算を行う場合の設計・プログラミング方針
1). 浮動小数点数の演算結果については有効桁数の範囲で評価する
浮動小数点数を扱うデータ型には、有効桁数が定められています。
例えば、.NET Framework の float 型の場合、有効桁数 7 桁、double 型なら有効桁数は 15 ~ 16 桁程度と定義されています。
浮動小数点数の特性上、有効桁数以上の桁数に対する演算結果は不定であり、この部分を評価対象とするようなシステムの設計やプログラミングは避ける必要があります。
例えば、小数値の一致を評価したい場合、以下のコードでは、前者の比較演算子 “==” を使用する方法では、通常、意図した結果を得られません。
(略)
2). それでも演算誤差の最小化が必要な場合は 10 進形式の型を利用を検討する
.NET Framework では、Decimal (10 進) 型が用意されております。
Decimal 型でも演算誤差は発生しますが、データの表現の仕方や演算の工夫により、通常の浮動小数点数と比較して誤差が発生しにくい特徴があります。
概要
この資料では、Microsoft Excel での浮動小数点数の格納方法および計算方法について説明します。丸めやデータの切り捨てに伴って、一部の数値または数式の結果に影響が及ぶ場合があります。
概要
Microsoft Excel では、浮動小数点数の格納および計算は、IEEE 754 の規格に沿って設計されています(略)
#その他参考
いまさら聞けないIT用語集 浮動小数点演算の単精度と倍精度って?
情報の表現
数値の表現
浮動小数点数型と誤差
暗黙の型変換
char < int < long int < float < double < long double
CDecで変数や数値を一つ一つ包まないとCDecが効かないのはこの暗黙の型変換が起きていると思われる。
###型変換で計算結果が変わる
上記サイトで考え方が紹介されていたものをVBScriptにしたもの
Clng(CLng(1) / Clng(3))
は1/3= 0.33333333333...なので整数部が0のため、0になる。
WScript.Echo Clng(CLng(1) / Clng(3))
WScript.Echo Clng(1/3) ' 0
WScript.Echo Clng CLng(1) / Clng(3)
WScript.Echo CCur(CSng(1) / CSng(3))
> = Clng(CLng(1) / Clng(3))