VBAで四捨五入を行うには
VBAには四捨五入を行う関数は用意されていません。近い動きするVBA関数にROUND関数がありますが、四捨五入とは違う動きになります。
そのため別の方法を考える必要があります。方法として2つあります。
1つは正しい四捨五入を行ってくれるワークシート関数のROUND関数を使う方法で、もう1つは自作で四捨五入関数を作成することです。
それぞれについて紹介しますが、自作の四捨五入関数の方が圧倒的に処理速度が速いです。もし大量の四捨五入計算が必要な場合は自作関数の利用を検討してください。
ワークシート関数のROUND関数を使う方法
ワークシート関数を使う場合、書き方が2種類あります。Evaluateメソッドで書く方法と[]で書く方法です。どちらで書いても結果は変わりません。
以下は555.555をROUND関数で四捨五入するサンプルコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
Sub WSRoundTest() Dim v Dim d Dim i '// EvaluateメソッドでVBA関数を使用 d = Application.Evaluate("ROUND(555.555, 0)") Debug.Print d '// 556 '// []でVBA関数を使用 d = [ROUND(555.555,1)] Debug.Print d '// 555.6 v = 555.555 '// 555.555を桁位置-4から4までループ処理 For i = -4 To 4 d = Application.Evaluate("ROUND(""" & v & """, """ & i & """)") '// RoundEx(555.555, -4) = 0 '// RoundEx(555.555, -3) = 1000 '// RoundEx(555.555, -2) = 600 '// RoundEx(555.555, -1) = 560 '// RoundEx(555.555, 0) = 556 '// RoundEx(555.555, 1) = 555.6 '// RoundEx(555.555, 2) = 555.56 '// RoundEx(555.555, 3) = 555.555 '// RoundEx(555.555, 4) = 555.555 Debug.Print "RoundEx(" & v & ", " & i & ") = " & d Next v = -555.555 '// -555.555を桁位置-4から4までループ処理 For i = -4 To 4 d = Application.Evaluate("ROUND(""" & v & """, """ & i & """)") '// RoundEx(-555.555, -4) = 0 '// RoundEx(-555.555, -3) = -1000 '// RoundEx(-555.555, -2) = -600 '// RoundEx(-555.555, -1) = -560 '// RoundEx(-555.555, 0) = -556 '// RoundEx(-555.555, 1) = -555.6 '// RoundEx(-555.555, 2) = -555.56 '// RoundEx(-555.555, 3) = -555.555 '// RoundEx(-555.555, 4) = -555.555 Debug.Print "RoundEx(" & v & ", " & i & ") = " & d Next End Sub |
四捨五入関数を自作する
ワークシート関数のROUND関数と同じ使い方をする関数を自作すると以下のコードのような感じになります。
四捨五入の考え方は判定桁の値が5以上であれば繰り上がり、5未満であれば繰り上がらない、となります。あとは判定桁位置とマイナスの場合の考慮を入れればOKです。
Double型同士の引き算がちょうど0.5になる場合は誤差により0.5にならず0.4999999・・となるため21行目の0.5の判定をわざと誤差を考慮して0.499としています。
Double型の代わりにCurrency型を使えば誤差は無くなりますが、小数部分が4桁までのためDouble型を使っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
'// a_number 数値 '// a_digits 桁数 Function RoundEx(a_number As Double, a_digits As Integer) As Double Dim num As Double '// 引数数値に桁数の乗数で調整した値 Dim dInt As Double '// 整数部分 Dim iSign '// 正負 '// 引数数値を桁数の乗数で調整 num = a_number * (10 ^ (1 * a_digits)) '// 正数 If num > 0 Then iSign = 1 '// 負数 Else iSign = -1 End If '// 整数部分を取得 dInt = Fix(num) '// 小数値の絶対値が0.5以上の場合 If Abs(num - dInt) >= 0.499 Then '// 繰り上げた値を返す RoundEx = dInt + iSign '// 小数値の絶対値が0.5未満の場合 Else '// 小数値を除いた整数を返す RoundEx = dInt End If '// 調整した値を元に戻す RoundEx = RoundEx * (10 ^ (-1 * a_digits)) End Function |
使い方
RoundEx関数の使い方です。
1番目の引数に元の数値をセットし、2番目の引数に繰り上げ対象の桁位置を指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
Sub RoundExTest() Dim d d = RoundEx(1.55, 0) Debug.Print d '// 2 Debug.Print RoundEx(-555.555, -4) '// 0 Debug.Print RoundEx(-555.555, -3) '// -1000 Debug.Print RoundEx(-555.555, -2) '// -600 Debug.Print RoundEx(-555.555, -1) '// -560 Debug.Print RoundEx(-555.555, 0) '// -556 Debug.Print RoundEx(-555.555, 1) '// -555.6 Debug.Print RoundEx(-555.555, 2) '// -555.56 Debug.Print RoundEx(-555.555, 3) '// -555.555 Debug.Print RoundEx(-555.555, 4) '// -555.555 Debug.Print RoundEx(-1.5, 0) '// -2 Debug.Print RoundEx(-1.4, 0) '// -1 Debug.Print RoundEx(-1, 0) '// -1 Debug.Print RoundEx(-0.5, 0) '// -1 Debug.Print RoundEx(-0.4, 0) '// 0 Debug.Print RoundEx(0, 0) '// 0 Debug.Print RoundEx(0.4, 0) '// 0 Debug.Print RoundEx(0.5, 0) '// 1 Debug.Print RoundEx(1, 0) '// 1 Debug.Print RoundEx(1.4, 0) '// 1 Debug.Print RoundEx(1.5, 0) '// 2 End Sub |
マイナス値の四捨五入の考え方
マイナス値の場合の四捨五入は、絶対値(プラス値)として四捨五入を行い、そのあとでマイナスにするという計算になります。この考え方はJIS規格の「JISZ8401」で示されています。
日本工業標準調査会のサイト
https://www.jisc.go.jp/app/jis/general/GnrJISSearch.html
このサイトで「Z8401」を検索する出てきます。
値の丸め方について正数で記載がされているのですが、附則部分に「負の数値を対象とする場合は、その絶対値に適用する」との内容が記載されています。ということは、マイナスの場合は一度絶対値として計算して、マイナス化する、と読めます。
なので、1.5を四捨五入すると2になるのであれば、-1.5は一度絶対数の1.5として考えて、四捨五入の2にして、マイナス化の-2となります。
ワークシート関数のROUND関数と自作関数の処理速度比較
実はこの速度比較を行うまで、ワークシート関数のROUND関数の方が処理は速いだろうなあ、と思いながらテストコードを書いてました。
ところが実際に比較してみると圧倒的に自作のRoundEx関数の方が速く、あ、そうなの?と思ってしまいました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
Sub RoundSpeedTest() Dim i Dim d Debug.Print Timer '// EvaluateでのROUND関数 For i = 1 To 100000 d = Application.Evaluate("ROUND(555.555, 0)") Next Debug.Print Timer '// WorksheetFunctionでのRound関数 For i = 1 To 100000 d = Application.WorksheetFunction.Round(555.555, 0) Next Debug.Print Timer '// 自作関数 For i = 1 To 100000 d = RoundEx(555.555, 0) Next Debug.Print Timer End Sub |
計測結果は以下のようになりました。
Application.Evaluate関数でのワークシート関数のROUND関数を1万回実行:5.68秒
Application.WorksheetFunction.Round関数を1万回実行:0.31秒
自作のRoundEx関数を同じく1万回実行すると、0.03秒。
ここまで違うとは思ってもみませんでした。Evaluate関数でのループ呼び出しは処理時間が掛かりますが、WorksheetFunction.Round関数と比べても自作の方が速いのは以外でした。