- ベストアンサー
VBAで一時的にオーバーフローを回避する方法
- VBAで数値の演算を行う際に、Long型の範囲を超えてしまう場合にエラーが発生することがあります。
- オーバーフローを回避するためには、事前に演算結果が範囲内に収まるかどうかを判定する必要があります。
- 適切な判定方法としては、演算前に演算子を使って範囲内に収まるかどうかを確認する方法や、演算後に結果を範囲内に収める方法があります。
- みんなの回答 (7)
- 専門家の回答
質問者が選んだベストアンサー
num1,num2を16ビット.8ビット,8ビットに分解します num1=n1h * 2^16+n1m*2^8+n1l num2=n2h * 2^16+n2m*2^8+n2l num1*num2を計算すると (n1h*n2h)*2^32 +(n1h*n2m+n1m*n2h)*2^24 +(n1h*n2l+n1m*n2m+n1l*n2h)*2^16 +(n1m*n2l+n1l*n2m)*2^8 + n1l*n2l となります。 (n1h*n2h)*2^32 から、 n1h*n2h<>0 ならば、33ビット以上が確実です。 n1h*n2h=0なら、残りが32ビット以上(2^31以上)ならMAXLONG超となります。 n1h*n2m+n1m*n2hは 16bit*8bitで最大24bit、24bit+24bitで最大25bitなのでオーバーフローはありません この値を m3=m3h*2^7+m3l (m3lは7ビット分) とすると +(n1h*n2m+n1m*n2h)*2^24 = (m3h*2^7+m3l ) * 2^24 = m3h*2^31 + m3l * 2^24 よって、m3h>0ならMAXLONG超となります。 同様に +(n1h*n2l+n1m*n2m+n1l*n2h)*2^16 は 24bit+24bit+16bitで最大26bit 。 m2=m2h*2^15+m2lとすると = (m2h*2^15+m2l ) * 2^16 =m2h*2^31+m2l*2^16 m3l * 2^24+m2l*2^16 = (m3l*2^8+m2l)*2^16 k=m3l*2^8+m2l = kh * 2^15+kl とすると (m3l*2^8+m2l)*2^16 = kh*2^31+kl*2^16 +(n1m*n2l+n1l*n2m)*2^8 + n1l*n2l はオーバーフロー無しで計算できるので kl*2^16 +(n1m*n2l+n1l*n2m)*2^8 + n1l*n2l > MAXLONG kl*2^16 > MAXLONG - ((n1m*n2l+n1l*n2m)*2^8 + n1l*n2l) これは、大小判定のみにして、余計な計算を省いたものです。 「'数値を減らす処理」が「オーバーフローした桁を無視する」なら、 -MAXLONG + kl*2^16 +(n1m*n2l+n1l*n2m)*2^8 + n1l*n2l で求められます。 しかし、resultから足したり割ったりするなら、上記で計算を省略した X * 2^31 の部分が必要です。 こちらだと、「多倍長数」「任意精度数」と呼ばれる手法が必要です。 例えば num1=n1(0)+n1(1)*2^8+n1(2)*2^16+n1(3)*2^24 として result=num1+num2 は d=n1(0)+n2(0) r(0)=d mod 256 ' dの下8bit c = d \ 256 '繰り上がり分 d=n1(1)+n2(1)+c '繰り上がり分を足す r(1)=d mod 256 c = d \ 256 .... と筆算の要領で「桁」毎に計算します。 あるいは、外部の多倍長精度ライブラリをアドオン等の形でVBAに取り込む方法があります。 試していませんが、検索で最上位に来たものです。 http://supermab.com/biginteger.html
その他の回答 (6)
- tatsu99
- ベストアンサー率52% (391/751)
#5の方へ よこからで、恐縮ですが、 私も、当初、通貨型を考えたのですが 通貨型は整数で、15桁~16桁でオーバーフローします。 従って、 Dim work As Currency work = CCur(num1) * CCur(num2) で、num1とnum2がMAXLONGに近い値をとる場合、 work = CCur(num1) * CCur(num2) のところで、エラーが発生します。 従って、今回の問題の完全な解決にはなりませんでした。
- Hayashi_Trek
- ベストアンサー率44% (366/818)
作業変数として通貨型を使うのはどうでしょう? Dim MAXLONG As Long MAXLONG = &H7FFFFFFF 'Long型の正の数の上限です。 Dim work As Currency work = CCur(num1) * CCur(num2) If ( work > MAXLONG) Then End If
お礼
ありがとうございます。 いろいろ試しているのですが、もはや型だけの問題ではなく、そもそもオーバーフローさせないしくみが必要なようです。
- tatsu99
- ベストアンサー率52% (391/751)
以下のようにしてみてはいかがでしょうか。 num1*num2 を行うfunction を作成する。 OKの場合、その結果を返す。 エラー(オーバーフロー)の場合、-1を返す。 以下、そのサンプルです。 ---------------------------------------- Option Explicit Sub test() Dim MAXLONG As Long Dim num1 As Long Dim num2 As Long Dim result As Long MAXLONG = &H7FFFFFFF num1 = 3456 num2 = 34567 result = mult(num1, num2) If (result = -1) Then MsgBox ("Error:" & result) Else MsgBox ("OK:" & result) End If End Sub Function mult(num1 As Long, num2 As Long) As Long On Error GoTo error1 mult = num1 * num2 Exit Function error1: mult = -1 End Function ------------------------------------
- nishi6
- ベストアンサー率67% (869/1280)
こんな感じでどうでしょうか。 Sub CalcTest() Const MAXLONG As Long = &H7FFFFFFF Dim num1 As Long Dim num2 As Long Dim result As Long num1 = 123456 num2 = 134567 On Error GoTo ErrorTrp Do Until num1 * num2 <= MAXLONG '数値を減らす処理(例です) num1 = num1 - 10 num2 = num2 - 10 Loop result = num1 * num2 MsgBox num1 & " × " & num2 & " = " & result Exit Sub ErrorTrp: Resume Next End Sub
お礼
ありがとうございます。 エラー処理で分岐するところまでできましたが、数が大きすぎると数値を減らす処理のところでやはり溢れてしまいました。
- rinkun
- ベストアンサー率44% (706/1571)
num1*num2を計算した時点で(resultに代入するまでもなく)エラーになりそうな気もしますが・・・ とりあえずDouble型で計算して範囲チェックしてからLong型に代入しては? ただ32bit×32bitの全範囲で計算するなら、結果は64bit全範囲を取り得るのでDoubleでも正確には表せなくなる可能性がありますけど。 # Long内で処理できるように16bit単位で計算するとかした方が正解かも
お礼
ありがとうございます。 Doubleでもオーバーフローする値だと溢れてしまいました。
- f272
- ベストアンサー率46% (8469/18132)
ぜんぜん確認していないけど,例えば if (num1 > (MAXLONG / num2)) then '数値を減らす処理 else result = num1 * num2 endif で何とかなるんじゃないかな?
お礼
ありがとうございます。 ある程度の数ならできましたが、思いっきり大きな数になるとオーバーフローしてしまいました。
お礼
ありがとうございます。 実際に書いてはいませんがこれが正解だと思います。 結局、C言語でシンプルな処理を書いて(なぜかオーバーフローしませんでした)それをexe化してVBAからコマンドラインで呼び出し、その戻り値を受け取る形で落ち着きました。 なんなんでしょうねぇ。。。。