VBAで一時的にオーバーフローを回避する方法

このQ&Aのポイント
  • VBAで数値の演算を行う際に、Long型の範囲を超えてしまう場合にエラーが発生することがあります。
  • オーバーフローを回避するためには、事前に演算結果が範囲内に収まるかどうかを判定する必要があります。
  • 適切な判定方法としては、演算前に演算子を使って範囲内に収まるかどうかを確認する方法や、演算後に結果を範囲内に収める方法があります。
回答を見る
  • ベストアンサー

VBAで一時的にオーバーフローを回避したい

こんにちは とあるハッシュ値のようなものを作成するプロセスで困っています。 次のようなコードを書いています。 Dim MAXLONG As Long MAXLONG = &H7FFFFFFF 'Long型の正の数の上限です。 result = num1 * num2 If ( result > MAXLONG) Then '数値を減らす処理 計算結果が Long 型の最大値を超えている場合に上限に収まるよう調整する処理をしたいのですが、最大値を超えている時点で result 変数に格納できずにエラーになってしまいます。 変数を事前にすべて As Long で宣言し、数値ではなく &H付きの 16 進数で代入してみたり、Clng() で囲ってみたり・・・いろいろ試したのですができません。 最終的には Long 型の範囲内に収めてその数値を使いたいのですが、一時的に溢れたかどうかをどのように判定すればよいのでしょうか? 補足すべき事があれば教えてください。 Windows 7 Pro 64bit Excel 2007 (これは32bit)

質問者が選んだベストアンサー

  • ベストアンサー
  • kmee
  • ベストアンサー率55% (1857/3366)
回答No.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

tuktukrace
質問者

お礼

ありがとうございます。 実際に書いてはいませんがこれが正解だと思います。 結局、C言語でシンプルな処理を書いて(なぜかオーバーフローしませんでした)それをexe化してVBAからコマンドラインで呼び出し、その戻り値を受け取る形で落ち着きました。 なんなんでしょうねぇ。。。。

その他の回答 (6)

  • tatsu99
  • ベストアンサー率52% (391/751)
回答No.6

#5の方へ よこからで、恐縮ですが、 私も、当初、通貨型を考えたのですが 通貨型は整数で、15桁~16桁でオーバーフローします。 従って、 Dim work As Currency work = CCur(num1) * CCur(num2) で、num1とnum2がMAXLONGに近い値をとる場合、 work = CCur(num1) * CCur(num2) のところで、エラーが発生します。 従って、今回の問題の完全な解決にはなりませんでした。

回答No.5

作業変数として通貨型を使うのはどうでしょう? Dim MAXLONG As Long MAXLONG = &H7FFFFFFF 'Long型の正の数の上限です。 Dim work As Currency work = CCur(num1) * CCur(num2) If ( work > MAXLONG) Then End If

tuktukrace
質問者

お礼

ありがとうございます。 いろいろ試しているのですが、もはや型だけの問題ではなく、そもそもオーバーフローさせないしくみが必要なようです。

  • tatsu99
  • ベストアンサー率52% (391/751)
回答No.4

以下のようにしてみてはいかがでしょうか。 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)
回答No.3

こんな感じでどうでしょうか。 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

tuktukrace
質問者

お礼

ありがとうございます。 エラー処理で分岐するところまでできましたが、数が大きすぎると数値を減らす処理のところでやはり溢れてしまいました。

  • rinkun
  • ベストアンサー率44% (706/1571)
回答No.2

num1*num2を計算した時点で(resultに代入するまでもなく)エラーになりそうな気もしますが・・・ とりあえずDouble型で計算して範囲チェックしてからLong型に代入しては? ただ32bit×32bitの全範囲で計算するなら、結果は64bit全範囲を取り得るのでDoubleでも正確には表せなくなる可能性がありますけど。 # Long内で処理できるように16bit単位で計算するとかした方が正解かも

tuktukrace
質問者

お礼

ありがとうございます。 Doubleでもオーバーフローする値だと溢れてしまいました。

  • f272
  • ベストアンサー率46% (8023/17148)
回答No.1

ぜんぜん確認していないけど,例えば if (num1 > (MAXLONG / num2)) then '数値を減らす処理 else result = num1 * num2 endif で何とかなるんじゃないかな?

tuktukrace
質問者

お礼

ありがとうございます。 ある程度の数ならできましたが、思いっきり大きな数になるとオーバーフローしてしまいました。

関連するQ&A

  • VBA Hex関数

    Hex関数を使用して、16進から10進の処理をしようとしてますが 下記※のところで「型が一致しません」と出てしまいます。 色々変数の型を変えても、2つの※のどちらかで出てしまいます。 Dim r As Long Dim H2D As Long Dim CheckSum_Cal As Long For r = 1 To 10  H2D = Hex(Cells(5, r)) ※ CheckSum_Cal = CheckSum_Cal + H2D ※ Next r Cells(5, r)を別の変数に入れても同じ結果になります。 どなたかお詳しい方、ご教示お願いいたします。

  • VBAのデータ型に関する質問です。 以下のような宣言と処理をしました。

    VBAのデータ型に関する質問です。 以下のような宣言と処理をしました。 Dim 数値1 As Integer Dim 数値2 As Integer Dim 数値3 As Long 数値1=Range("b5").Value 数値2=Range("c5").Value 数値3=数値1*数値2 この式では、数値3がLongの型ではなくIntegerになってしまいオーバーフローのエラーになってしまいます。(10000×5の場合等)Long型のまま代入する方法がありましたら教えてください。よろしくお願いいたします。

  • エクセルVBA 数値の桁数を揃え、つなぎたい

    エクセルVBA、初心者です。 以下の記述でつまづいています。 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ Dim i As Long 'i は行数を For~Nextで変数にしてます Dim Hbc As Long, Hbc2 As Long 'Hbc は最大11桁の変数 Dim Tsc As Long, Tsc2 As Long 'Tsc は最大5桁の変数 Dim Cd As Long, Cd2 As Long 'Cd は最大4桁の変数 Dim HTC As Long      |      |    長い為、省略      |      | Hbc2 = 100000000000 + Hbc '(1) Tsc2 = 100000 + Tsc Cd2 = 10000 + Cd HTC = Right(Hbc2, 11, 11) & Right(Tsc2, 5, 5) & Right(Cd2, 4, 4) Wks1.Cells(i, 1).Value = HTC ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ Hbc、Tsc、Cd は、データベースから取得した数値が入りますが、 桁数がバラバラな為、頭に0を表示してそれぞれの最大桁数に揃えたいのです。 桁数を揃えた後、3つをつないで HTC とし、i 行目の1列目に表示しようとしています。 下記がつまづいて、解決できないでいるトコロです。 ************問題点************ (1)の行の Hbc2 = 100000000000 + Hbc という記述が Hbc2 = 10000000000# + Hbc に置き換わってしまいます。 *************************** なにぶん、初心者で、単純な間違いかもしれませんが、 どうかご指導、宜しくお願いします。

  • VBAの内容について

    エクセルファイルのVBAを少し手直ししたいのですが、 超初心者で行き詰っております。 下記は全体の一部なのですが、、どんな内容を表しているのか 教えていただけないでしょうか? 調べてみても、変数を定義して、計算している(?) くらいしかわかりませんでした。 Public Sub abc計算() Dim vi As Class1 Set vi = New Class1 Dim k As Long For k = CLng(vix_ro) To 6 Step -1 If (vi.abc_flg(CLng(k - 1), 6) = True) Then si_to.Cells(CLng(k - 1), 6).Value = "○" End If Next End Sub excel2007 vista使用です。 どうぞよろしくお願い申し上げます。

  • VBAと罫線の作成

    こんにちは。 VBA勉強中で初心者です。 今、以下の上段画像のような表をエクセル2003 で作成しました。 下段画像はVBA実行後の表です。 表は 番号と氏名、合否の結果を入力した ものです。 今、行いたい処理なのですが (1) B列の氏名欄に対応する、連番を A列に表示したい。 (2) さらに、氏名欄が入っている部分を A列からD列まですべてを罫線(普通線) で囲みたい。 の2点です。 (1)はなんとかできたみたいのですが、 (2)で完全に手が止まってしまいました。 最終の行と列の取得が、すこしわからないので 教えていただければと思います。 よろしくお願いします。 Sub sample() Dim num As Integer '行変数を設定 Dim num2 As Integer '番号変数を設定 Dim i As Long 最終行の取得? num = 4 'B列4行目から調べる num2 = 1 '連番は「1」からスタート Range("A:a").Clear 'A列のデータをいったんクリアにする Do Until Cells(num, 2) = "" 'B列の氏名欄が空白なるまで処理を繰り返す Cells(num, 1) = num2 '連番「1」をB4にまず、代入する num = num + 1 '行を1行分下げる num2 = num2 + 1 '番号変数に1をプラスする Loop i = Range("B4").End(xlDown).Row End Sub

  • 日付を数値型にしたいけどうまくできない

    Sub さんぷる1() Dim testlong As Long testlong = CLng(("2012/12/14")) End Sub をすると、 型が一致しません。になりますが、なぜでしょう? VBAです。

  • 実数の整数部,小数部の取得

    OS:windows2000pro VB:VB6.0sp5 実数 num(変数) [as currency]  (但し 小数点以下2位まで)の値の 1.整数部 num_int(変数) [as long] 2.小数部 num_dec(変数) [as long] を上記変数に格納するのは、どのようにすればいいのでしょうか? (例1)num = 123.02 の場合、num_int=123, num_dec=2 (例2)num = 4.20 の場合、num_int= 4, num_dec=20 (例3)num = 0.23 の場合、num_int= 0, num_dec=23  よろしくお願いいたします

  • Excel vba 配列内の最大値を求めたいです。

    Excel vba をはじめて1ヵ月程度の初心者ですが、すいません質問させて下さい。 動的配列というのでしょうか? その配列の中の最大値を求めたいのですが、よくわかりません。 アクティブシート内の図形で最前面以外の図形を消去しようと思ってます。 マクロで最大値の取得ができないので、暫定的にシートに計算させてしまっているのですが、マクロ上で最大値を求める方法がわかりません。 Sub testSZ() Dim Num As Integer, Sum As Integer, Shp As Shape, Ary() As Variant, Mxm As Long '++++++++++↓アクティブシート内の図形をカウント:=Sum Sum = 0 For Each Shp In ActiveSheet.Shapes Sum = Sum + 1 Next Shp '++++++++++↓配列の数を決定 ReDim Ary(1, Sum) '++++++++++↓配列に図形と図形のZオーダーを設定 Num = 0 For Each Shp In ActiveSheet.Shapes Shp.Select Set Ary(0, Num) = Shp Ary(1, Num) = Selection.ShapeRange.ZOrderPosition Num = Num + 1 Next Shp '++++++++++↓配列内のZオーダー最大値を取得 Mxm = Application.WorksheetFunction.Max '++++++++++↓最前面の図形以外を消去 Num = 0 Do If Mxm > Ary(1, Num) Then Ary(0, Num).Delete End If Num = Num + 1 Loop Until Sum = Num Erase Shp End Sub Application.WorksheetFunction.Max[] の[]にAryやAry(1,Num)をやってみたりしたのですが、 できないです。 どなたか御教授おねがい致します!!

  • 2つ以上の変数を比較して最大数を求めたい

    Sub 最大数() Dim a As Long Dim b As Long Dim c As Long a = 10 b = 20 c = 30 ここで変数を比較するコード MsgBox "a,b,cの中で一番大きな数は です" End Sub のように変数を比較して、最大数を求めたいのです。 2つなら If a > b Then で結果を取得すればいいですが、 3つや4つやそれ以上になった時に、応用を利かせるため質問しました。 よろしくお願いします。

  • 変数の宣言の名称を教えてください。(Dim i As Long)

    Dim i As Longについて 「dim」→? 「i」→変数名 「As」→? 「Long」→変数の型 というように、dimとAsがなんと言えばいいのかわかりません。 アドバイスよろしくお願いします。