符号なし整数乗算オーバーフローを検出するにはどうすればよいですか?


618

私は、すべての解決策を見つけるためにC ++でプログラムを書いていたB = C、、BCが一緒にすべての桁を使用し0-9回だけを。プログラムは、値の上にループとB、そして、それは上の桁カウントルーチンを毎回走っ、BBの桁条件が満たされたかどうかを確認します。

しかし、スプリアス溶液を生成することができるbは整数制限をオーバーフロー。私は次のようなコードを使用してこれをチェックすることになりました:

unsigned long b, c, c_test;
...
c_test=c*b;         // Possible overflow
if (c_test/b != c) {/* There has been an overflow*/}
else c=c_test;      // No overflow

オーバーフローをテストするより良い方法はありますか?一部のチップにはオーバーフローが発生したときに設定される内部フラグがあることは知っていますが、CまたはC ++を介してアクセスされることはありません。


符号付き intオーバーフローはCおよびC ++では未定義の動作であるため、実際にそれを引き起こさずに検出する必要があることに注意してください。追加前の署名付きintオーバーフローについては、C / C ++での署名付きオーバーフローの検出を参照してください。


21
このテーマに関する有用である可能性がある情報:Seacordで「CおよびC ++でセキュアコーディング」の第5章- http://www.informit.com/content/images/0321335724/samplechapter/seacord_ch05.pdf SafeInt C ++のクラス- HTTP ://blogs.msdn.com/david_leblanc/archive/2008/09/30/safeint-3-on-codeplex.aspx - http://www.codeplex.com/SafeInt IntSafe Cのためのライブラリ: - [ blogs.msdn .com / michael_howard / archiv
マイケルバー

3
SeacordのSecure Codingは素晴らしいリソースですが、IntegerLibは使用しないでください。blog.regehr.org/archives/593を参照してください。
jww 2009

32
gccコンパイラー・オプション-ftrapvにより、(符号付き)整数オーバーフローでSIGABRTが生成されます。こちらをご覧ください
nibot 2012年

1
オーバーフローの質問には答えませんが、問題を解決するもう1つの方法は、GMPなどのBigNumライブラリを使用して、常に十分な精度があることを保証することです。十分な桁数を前もって割り当てておけば、オーバーフローを心配する必要はありません。
wrdieter 2013

1
彼の回答で@HeadGeekによって提供された情報も、私が言うこととほとんど同じです。ただし、追加が1つあります。乗算のオーバーフローを検出する方法は、おそらく最速です。ARMでは、HeadGeekの回答でコメントしたように、clz命令または__clz(unsigned)関数を使用して、数値のランク(その最上位ビットがどこにあるか)を判別できます。これがx86またはx64で利用できるかどうかは不明なので、そうではないと仮定し、最上位ビットを見つけると、最悪のlog(sizeof(int)*8)命令が実行されると言います。
nonsensickle 2013年

回答:


229

符号なし整数を使用しているようです。定義により、C(C ++についてはわかりません)、符号なし演算はオーバーフローしません...したがって、少なくともCの場合、あなたのポイントは意味がありません:)

符号付き整数では、オーバーフローが発生すると、未定義の動作(UB)が発生し、プログラムは何でも実行できます(たとえば、テストを決定的にレンダリングできません)。 

#include <limits.h>

int a = <something>;
int x = <something>;
a += x;              /* UB */
if (a < 0) {         /* Unreliable test */
  /* ... */
}

適合プログラムを作成するには、オーバーフロー生成する前にオーバーフローをテストする必要があります。このメソッドは、符号なし整数でも使用できます。

// For addition
#include <limits.h>

int a = <something>;
int x = <something>;
if ((x > 0) && (a > INT_MAX - x)) /* `a + x` would overflow */;
if ((x < 0) && (a < INT_MIN - x)) /* `a + x` would underflow */;

// For subtraction
#include <limits.h>
int a = <something>;
int x = <something>;
if ((x < 0) && (a > INT_MAX + x)) /* `a - x` would overflow */;
if ((x > 0) && (a < INT_MIN + x)) /* `a - x` would underflow */;

// For multiplication
#include <limits.h>

int a = <something>;
int x = <something>;
// There may be a need to check for -1 for two's complement machines.
// If one number is -1 and another is INT_MIN, multiplying them we get abs(INT_MIN) which is 1 higher than INT_MAX
if ((a == -1) && (x == INT_MIN)) /* `a * x` can overflow */
if ((x == -1) && (a == INT_MIN)) /* `a * x` (or `a / x`) can overflow */
// general case
if (a > INT_MAX / x) /* `a * x` would overflow */;
if ((a < INT_MIN / x)) /* `a * x` would underflow */;

除算(INT_MINおよびの-1特殊な場合を除く)では、INT_MINまたはを超える可能性はありませんINT_MAX


97
符号なし整数は、C ++でも厳密にオーバーフローしません(ISO / IEC 14882:2003 3.9.1.4)。質問での「オーバーフロー」の使用は、より一般的な意味であり、符号なしの型の明確に定義されたラッピングを含めることを意図したものでした。正の整数mod 2 ^ 32(または2 ^ 64)。数学的な無限サイズの整数の振る舞いからの逸脱としてのオーバーフローと、言語の未定義の動作としてのオーバーフローとの違いは、明確にされることはほとんどありません。
クリスジョンソン、

15
そのテストは必須ではありませんx >= 0- x > 0十分です(もしそうならx == 0x + a明らかな理由でオーバーフローすることはできません)。
ca

2
@pmg、標準からのサポート引用はありますか?
パセリエ2013

5
私はこのアプローチが好きです...ただし、注意してください。乗算オーバーフローの検出では、正のxを想定しています。x == 0の場合はゼロ検出で除算され、負のxの場合は常にオーバーフローが誤って検出されます。
フランツD.

4
if ((a < INT_MIN / x))テストは遅すぎます。if (x == -1) テストが最初に必要とされています。
chux-モニカを2017年

164

あります操作がオペランドで最上位1ビットの位置と少し基本的なバイナリ数学の知識を使用して、オーバーフローする可能性があるかどうかを決定する方法は。

さらに、任意の2つのオペランドは、最大で1つのビットよりも最大で1ビット多くなります。例えば:

bool addition_is_safe(uint32_t a, uint32_t b) {
    size_t a_bits=highestOneBitPosition(a), b_bits=highestOneBitPosition(b);
    return (a_bits<32 && b_bits<32);
}

乗算の場合、2つのオペランドは(最大で)オペランドのビットの合計になります。例えば:

bool multiplication_is_safe(uint32_t a, uint32_t b) {
    size_t a_bits=highestOneBitPosition(a), b_bits=highestOneBitPosition(b);
    return (a_bits+b_bits<=32);
}

同様に、結果の最大サイズを次abように累乗して見積もることができます。

bool exponentiation_is_safe(uint32_t a, uint32_t b) {
    size_t a_bits=highestOneBitPosition(a);
    return (a_bits*b<=32);
}

(もちろん、ターゲット整数をビット数に置き換えます。)

数値の最上位の1ビットの位置を決定する最速の方法がわからない。これがブルートフォースメソッドです。

size_t highestOneBitPosition(uint32_t a) {
    size_t bits=0;
    while (a!=0) {
        ++bits;
        a>>=1;
    };
    return bits;
}

完璧ではありませんが、操作を実行する前に2つの数値がオーバーフローする可能性があるかどうかがわかります。highestOneBitPosition関数のループのため、提案した方法で結果を単にチェックするよりも速いかどうかはわかりませんが、特にそうである可能性があります(特に、オペランドのビット数が事前にわかっている場合)。


98
もちろん、highestOneBitPositionの名前をlogに変更することもできます:)
Oliver Hallam

37
はい、これはと同じ操作ですlog2が、数学的なバックグラウンドを持っていなかった人にとっては、必ずしもそれほど明白ではありません。
オタクヘッド、

48
このアルゴリズムは安全な答えを過小評価していませんか?2 ^ 31 + 0は、highestOneBitPosition(2 ^ 31)= 32であるため、安全でないと検出されます。(2 ^ 32-1)* 1は、32 + 1> 32であるため、安全でないと検出されます。1^ 100は、1 * 100以降、安全でないと検出されます。 > 32.
クラヘイ

19
あなたのmultiplication_is_safe 0x8000 * 0x10000オーバーフローによると(ビット位置は16 + 17 = 33、つまり320x8000 * 0x10000 = 0x80000000を超えています)、オーバーフローしないのは明らかですが、それでもまだ符号なし32ビットintに適合します。これは、このコードが機能しない5つの例のうちの1つにすぎません。0x8000 * 0x10001、...
Michi

13
@GT_mh:あなたのポイントは?言ったように、それは完璧ではありません。何か安全であると決定的に言うのは経験則ですが、すべての計算が完全な計算をしないと大丈夫かどうかを判断する方法はありません。0x8000 * 0x10000大丈夫であることが判明したとしても、この定義では「安全」ではありません。
Geekヘッド

147

Clang 3.4+GCC 5+は、チェック付き算術ビルトインを提供します。特にビットテストの安全性チェックと比較すると、この問題に対する非常に高速なソリューションを提供します。

OPの質問の例では、次のように機能します。

unsigned long b, c, c_test;
if (__builtin_umull_overflow(b, c, &c_test))
{
    // Returned non-zero: there has been an overflow
}
else
{
    // Return zero: there hasn't been an overflow
}

Clangのドキュメントではc_test、オーバーフローが発生した場合にオーバーフローした結果が含まれるかどうかは指定されていませんが、GCCのドキュメントには含まれていると記載されています。これらの2つは__builtin互換性があることを好むので、これがClangの動作方法でもあると想定してもおそらく安全です。

ある__builtinINTサイズ、長いサイズ、長い長いサイズの符号付きおよび符号なしの変異体とオーバーフローすることができ、各算術演算(加算、減算、乗算)、のため。名前の構文は次のとおりです__builtin_[us](operation)(l?l?)_overflow

  • u以下のための符号なしまたはsのために署名しました
  • 操作はaddsubまたはのいずれかmulです。
  • l接尾辞がないことは、オペランドがintsであることを意味します。1つllong; 2つはl意味しlong longます。

したがって、チェックされた符号付き長整数加算の場合は、になります__builtin_saddl_overflow。完全なリストは、Clangのドキュメントページにあります

GCC 5+とClang 3.8+は、値のタイプを指定せずに機能する一般的なビルトインを追加で提供します:__builtin_add_overflow__builtin_sub_overflowおよび__builtin_mul_overflow。これらは、よりも小さいタイプでも機能しますint

ビルトインは、プラットフォームに最適なレベルまで低下します。x86では、キャリー、オーバーフロー、および署名のフラグをチェックします。

Visual Studioのcl.exeには、直接同等のものはありません。含む符号なしの加算と減算については、<intrin.h>あなたが使用できるようになりますaddcarry_uNNsubborrow_uNN(NNはビット数であり、同様にaddcarry_u8またはsubborrow_u64)。彼らの署名は少し鈍いです:

unsigned char _addcarry_u32(unsigned char c_in, unsigned int src1, unsigned int src2, unsigned int *sum);
unsigned char _subborrow_u32(unsigned char b_in, unsigned int src1, unsigned int src2, unsigned int *diff);

c_in/ b_inは入力時のキャリー/ボローフラグで、戻り値は出力時のキャリー/ボローです。符号付き演算または乗算に相当するものはないようです。

それ以外の場合は、Clang for Windowsが本番環境に対応している(Chromeには十分対応可能)ため、これもオプションになる可能性があります。


__builtin_sub_overflowClang 3.4には絶対にありません。
リチャードクック

2
@RichardCook、少し時間がかかりましたが、Clangにはバージョン3.9のジェネリックビルトインがあります。
zneak 2016年

@tambre、あるとは思いません。
zneak 2016年

4
docsによると、__builtin_add_overflow友達はすでにClang 3.8で利用できるはずです。
Lekensteyn

2
ありがとう。これはうまくいきます。Visual C ++に対応する関数は何ですか?それらを見つけることができないようです。
Mudit Jain

53

一部のコンパイラは、CPUの整数オーバーフローフラグへのアクセスを提供します。これは、テストできますが、これは標準ではありません。

乗算を実行する前に、オーバーフローの可能性をテストすることもできます。

if ( b > ULONG_MAX / a ) // a * b would overflow

11
...または、numeric_limits <TYPE> :: max()を使用
Jonas Gulle

20
a = 0を処理することを忘れないでください-その場合、除算は中断します。
Thelema

16
@Thelema:「a = 0を処理することを忘れないでください」-およびINT_MIN / -1。
JWW

1
もしもb == ULONG_MAX / a?その場合、残差なしでa分割されるULONG_MAXことを考えると、それはまだ適合できます。
豚1​​4

おかしなことに、パフォーマンスに関しては、乗算は除算に比べてかなり高速であり、乗算ごとに除算を追加します。これは解決策のように聞こえません。
DrumM

40

警告:でコンパイルすると、GCCはオーバーフローチェックを最適化でき-O2ます。このオプションで-Wallは、次のような場合に警告が表示されます

if (a + b < a) { /* Deal with overflow */ }

この例ではありません:

b = abs(a);
if (b < 0) { /* Deal with overflow */ }

安全な方法は、CERTペーパーで説明されているように、オーバーフローが発生する前に確認することであり、体系的に使用するのは非常に面倒です。

でコンパイル -fwrapvすると問題は解決しますが、一部の最適化が無効になります。

より良い解決策が必要です。オーバーフローが発生しないことに依存する最適化を行う場合、コンパイラーはデフォルトで警告を発行する必要があると思います。現在の状況では、コンパイラーはオーバーフローチェックを最適化することができますが、これは私の意見では受け入れられません。


8
コンパイラはこれを符号付き整数型でのみ実行できることに注意してください。オーバーフローは、符号なし整数型に対して完全に定義されています。それでも、はい、それはかなり危険な罠です!
SamB

1
「オーバーフローが発生しないことに依存する最適化を行う場合、コンパイラーはデフォルトで警告を発行する必要があると思います。」-それでfor(int k = 0; k < 5; k++) {...}警告を発すべきですか?
user253751

2
@immibis:なぜそうすべきなのか?の値はkコンパイル時に簡単に決定できます。コンパイラは、何も仮定する必要はありません。
MikeMB 2016年

2
@immibis:上記を引用すると、「コンパイラーは、オーバーフローが発生しないことに依存する最適化行う場合、デフォルトで警告を発行する必要があると思います。」
MikeMB 2016年

1
@MikeMB ?のn下位5ビットのみを使用するシフト命令を発行する前に、コンパイラが32未満であることを確認する必要がない最適化n
user253751 2016年

30

Clangは、符号付き整数と符号なし整数の両方の動的オーバーフローチェックをサポートするようになりました。-fsanitize = integerスイッチを参照してください。現時点では、デバッグ目的で完全にサポートされた動的オーバーフローチェックを備えた唯一のC ++コンパイラです。


25

多くの人がオーバーフローに関する質問に答えたようですが、私は彼の元の問題に対処したいと思いました。問題はbを見つけることだと彼は言ったすべての数字が繰り返されることなく使用されるような = c。わかりました、それは彼がこの投稿で尋ねたものではありませんが、問題の上限を調べ、オーバーフローを計算したり検出したりする必要がないと結論付ける必要があると私はまだ思います(注:私は上手ではありません)数学では、これを段階的に行いましたが、最終結果は非常に単純で、これは単純な式を持っているかもしれません)。

重要な点は、問題がa、b、cのいずれかに必要とする上限は98.765.432であるということです。とにかく、問題を簡単な部分と重要でない部分に分けることから始めます。

  • x 0 == 1(9、8、7、6、5、4、3、2のすべての順列は解です)
  • x 1 == x(解決策なし)
  • 0 b == 0(解決策なし)
  • 1 b == 1(解決策なし)
  • a b、a> 1、b> 1(重要ではない)

ここで、他の解決策はあり得ず、置換のみが有効であることを示す必要があります(そして、それらを印刷するコードは簡単です)。上限に戻ります。実際には上限はc≤98.765.432です。これは、8桁の最大数であるため、上限です(10桁の合計から各aとbの1を引いたもの)。この上限はcのみです。aとbの境界は、bを2から上限まで変化させて計算できるように、指数関数的増加のためにはるかに低くなければならないためです。

    9938.08^2 == 98765432
    462.241^3 == 98765432
    99.6899^4 == 98765432
    39.7119^5 == 98765432
    21.4998^6 == 98765432
    13.8703^7 == 98765432
    9.98448^8 == 98765432
    7.73196^9 == 98765432
    6.30174^10 == 98765432
    5.33068^11 == 98765432
    4.63679^12 == 98765432
    4.12069^13 == 98765432
    3.72429^14 == 98765432
    3.41172^15 == 98765432
    3.15982^16 == 98765432
    2.95305^17 == 98765432
    2.78064^18 == 98765432
    2.63493^19 == 98765432
    2.51033^20 == 98765432
    2.40268^21 == 98765432
    2.30883^22 == 98765432
    2.22634^23 == 98765432
    2.15332^24 == 98765432
    2.08826^25 == 98765432
    2.02995^26 == 98765432
    1.97741^27 == 98765432

たとえば、最後の行に注意してください:1.97 ^ 27〜98Mと表示されています。したがって、たとえば、1 ^ 27 == 1および2 ^ 27 == 134.217.728であり、9桁(2> 1.97なので、テストする必要があるものよりも実際に大きい)であるため、これは解決策ではありません。ご覧のように、aとbのテストに使用できる組み合わせは本当に小さいものです。b == 14の場合、2と3を試す必要があります。b== 3の場合、2から開始して462で停止します。すべての結果は、約98M未満であることが認められています。

上記のすべての組み合わせをテストして、数字を繰り返さないものを探します。

    ['0', '2', '4', '5', '6', '7', '8'] 84^2 = 7056
    ['1', '2', '3', '4', '5', '8', '9'] 59^2 = 3481
    ['0', '1', '2', '3', '4', '5', '8', '9'] 59^2 = 3481 (+leading zero)
    ['1', '2', '3', '5', '8'] 8^3 = 512
    ['0', '1', '2', '3', '5', '8'] 8^3 = 512 (+leading zero)
    ['1', '2', '4', '6'] 4^2 = 16
    ['0', '1', '2', '4', '6'] 4^2 = 16 (+leading zero)
    ['1', '2', '4', '6'] 2^4 = 16
    ['0', '1', '2', '4', '6'] 2^4 = 16 (+leading zero)
    ['1', '2', '8', '9'] 9^2 = 81
    ['0', '1', '2', '8', '9'] 9^2 = 81 (+leading zero)
    ['1', '3', '4', '8'] 3^4 = 81
    ['0', '1', '3', '4', '8'] 3^4 = 81 (+leading zero)
    ['2', '3', '6', '7', '9'] 3^6 = 729
    ['0', '2', '3', '6', '7', '9'] 3^6 = 729 (+leading zero)
    ['2', '3', '8'] 2^3 = 8
    ['0', '2', '3', '8'] 2^3 = 8 (+leading zero)
    ['2', '3', '9'] 3^2 = 9
    ['0', '2', '3', '9'] 3^2 = 9 (+leading zero)
    ['2', '4', '6', '8'] 8^2 = 64
    ['0', '2', '4', '6', '8'] 8^2 = 64 (+leading zero)
    ['2', '4', '7', '9'] 7^2 = 49
    ['0', '2', '4', '7', '9'] 7^2 = 49 (+leading zero)

問題に一致するものはありません(「0」、「1」、...、「9」がないことでも確認できます)。

それを解決するコード例を以下に示します。また、Pythonで記述されていることに注意してください。これは、任意の精度の整数が必要なため(コードは9800万を超える数値を計算しないため)、テストの量が非常に少ないため、高水準言語を使用する必要があることがわかりました。組み込みのコンテナーとライブラリーを利用します(また、コードには28行あります)。

    import math

    m = 98765432
    l = []
    for i in xrange(2, 98765432):
        inv = 1.0/i
        r = m**inv
        if (r < 2.0): break
        top = int(math.floor(r))
        assert(top <= m)

        for j in xrange(2, top+1):
            s = str(i) + str(j) + str(j**i)
            l.append((sorted(s), i, j, j**i))
            assert(j**i <= m)

    l.sort()
    for s, i, j, ji in l:
        assert(ji <= m)
        ss = sorted(set(s))
        if s == ss:
            print '%s %d^%d = %d' % (s, i, j, ji)

        # Try with non significant zero somewhere
        s = ['0'] + s
        ss = sorted(set(s))
        if s == ss:
            print '%s %d^%d = %d (+leading zero)' % (s, i, j, ji)

1
上限として9.876.543.210を使用しないのはなぜですか?
トムロジェロ2018年

3
方程式の左辺には2桁を使用する必要があるためです。
hdante 2018年

2
違いはありませんが、LHSの値が1より大きいと述べたとおり、上限は実際には98765410と見なすことができます
Paul Childs

24

これは、質問に対する「非ポータブル」な解決策です。Intel x86およびx64 CPUには、いわゆるEFLAGS-registerがあり、整数演算ごとにプロセッサーによって書き込まれます。ここでは詳細な説明は省略します。関連するフラグは、「オーバーフロー」フラグ(マスク0x800)と「キャリー」フラグ(マスク0x1)です。それらを正しく解釈するには、オペランドが符号付き型か符号なし型かを検討する必要があります。

C / C ++からフラグをチェックする実際的な方法を次に示します。次のコードは、Visual Studio 2005以降(32ビットと64ビットの両方)、およびGNU C / C ++ 64ビットで機能します。

#include <cstddef>
#if defined( _MSC_VER )
#include <intrin.h>
#endif

inline size_t query_intel_x86_eflags(const size_t query_bit_mask)
{
    #if defined( _MSC_VER )

        return __readeflags() & query_bit_mask;

    #elif defined( __GNUC__ )
        // This code will work only on 64-bit GNU-C machines.
        // Tested and does NOT work with Intel C++ 10.1!
        size_t eflags;
        __asm__ __volatile__(
            "pushfq \n\t"
            "pop %%rax\n\t"
            "movq %%rax, %0\n\t"
            :"=r"(eflags)
            :
            :"%rax"
            );
        return eflags & query_bit_mask;

    #else

        #pragma message("No inline assembly will work with this compiler!")
            return 0;
    #endif
}

int main(int argc, char **argv)
{
    int x = 1000000000;
    int y = 20000;
    int z = x * y;
    int f = query_intel_x86_eflags(0x801);
    printf("%X\n", f);
}

オペランドがオーバーフローせずに乗算された場合、からの戻り値は0になります。query_intel_eflags(0x801)つまり、キャリーフラグもオーバーフローフラグも設定されていません。提供されているmain()のサンプルコードでは、オーバーフローが発生し、両方のフラグが1に設定されています。このチェックはそれ以上の計算を意味するものではないため、非常に高速です。


21

テストするデータ型よりも大きいデータ型がある場合(32ビットの加算を実行し、64ビットの型がある場合など)、オーバーフローが発生したかどうかが検出されます。私の例は、8ビットの加算です。しかし、それは拡大することができます。

uint8_t x, y;    /* Give these values */
const uint16_t data16    = x + y;
const bool carry        = (data16 > 0xFF);
const bool overflow     = ((~(x ^ y)) & (x ^ data16) & 0x80);

これは、このページで説明されている概念に基づいています。http//www.cs.umd.edu/class/spring2003/cmsc311/Notes/Comb/overflow.html

32ビットの例で0xFF0xFFFFFFFF、とに0x80なり0x80000000、最終的ににuint16_tなりますuint64_t

:これは整数の加算/減算のオーバーフローをキャッチし、あなたの質問には乗算が含まれていることに気付きました。その場合、おそらく分割が最善のアプローチです。これは一般callocに、最終的なサイズを取得するために乗算されるときにパラメーターがオーバーフローしないことを実装が確認する方法です。


リンクが壊れています:HTTP 403:Forbidden
Peter Mortensen

18

最も簡単な方法は、unsigned longsをunsigned long longs に変換し、乗算して、結果を0x100000000LLと比較することです。

これは、例で行ったように、除算を行うよりも効率的であることがわかります。

ああ、それはCとC ++の両方で機能します(両方に質問のタグを付けたので)。


ちょうどglibcのマニュアルを見ているところです。のFPE_INTOVF_TRAP一部として、整数オーバーフロートラップ()についての言及がありSIGFPEます。マニュアルの厄介な部分は別として、それは理想的です:

FPE_INTOVF_TRAP 整数オーバーフロー(ハードウェア固有の方法でオーバーフロートラップを有効にしない限り、Cプログラムでは不可能)。

ちょっと残念。


4
ええと...私が言わなかったことは、長いlong intをすでに使用している、より大きな数の問題を解決するプログラムを書く準備としてこの質問をしているということでした。long long intは(申し立てによると)C ++標準にはないため、混乱を避けるために32ビットバージョンを使用しました。
Chris Johnson、

ULONG_MAXハードコーディングよりも入力が簡単で移植性の高い方法を使用することをお勧めし0x100000000ます。
jw013 2013年

24
これが動作したときにしないlonglong long同じ大きさ(例えば、多くの64ビットのコンパイラで)です。
interjay 2013

とにかく、オーバーフローについて通知するために信号に依存することは本当に遅いでしょう。
SamB 2014

@SamBオーバーフローが頻繁に発生すると予想された場合のみ。
user253751

17

これは、少なくとも加算のオーバーフローを検出する非常に高速な方法です。これにより、乗算、除算、べき乗が生じる可能性があります。

アイデアは、プロセッサーが値をゼロに戻すだけで、C / C ++が特定のプロセッサーから抽象化されるため、次のことが可能です。

uint32_t x, y;
uint32_t value = x + y;
bool overflow = value < (x | y);

これにより、オペランドの1つがゼロで1つがゼロでない場合でも、オーバーフローが誤って検出されず、以前に提案された多くのNOT / XOR / AND /テスト演算よりも大幅に高速になります。

指摘したように、このアプローチは他のより精巧な方法よりも優れていますが、依然として最適化可能です。以下は、最適化を含む元のコードのリビジョンです。

uint32_t x, y;
uint32_t value = x + y;
const bool overflow = value < x; // Alternatively "value < y" should also work

乗算オーバーフローを検出するより効率的で安価な方法は次のとおりです。

uint32_t x, y;
const bool overflow = (x >> 16U) * (y >> 16U);
uint32_t value = overflow ? UINT32_MAX : x * y;

これにより、オーバーフロー時にUINT32_MAX、または乗算の結果が発生します。この場合、符号付き整数に対して乗算を続行できるのは、厳密に定義されていない動作です。


計算理論が原因で同意しません。次の点を考慮してください。y> x、値のオーバーフロー、符号ビットが設定されているため(1 + 255、たとえば符号なし文字の場合)、yはxよりも大きいだけです。オーバーフローに=偽-したがって、論理の使用またはこの破損現象を防止するために、..
DX-MON

テストは、指定した数値(x:= 1、y:= 255、size = uint8_t)に対して機能します。値は0(1 + 255)で、0 <1はtrueです。それは実際にすべての数値ペアに対して機能します。
Gunther

うーん、あなたは良い点を作ります。私はまだorトリックを使用して安全性の側に固執していますが、優れたコンパイラはプロバイダーを最適化するため、結果はオーバーフローしない「0 + 4」などのオーバーフローしない数値を含め、すべての入力に対して確かに正しいです。
DX-MON

4
オーバーフローがある場合は、x+y>=256およびvalue=x+y-256。ので、y<256常に成り立つ、(Y-256)が負であるので、value < x常に真です。オーバーフローしない場合の証明は非常に似ています。
Gunther Piez

2
@ DX-MON:前の加算のキャリービットもある場合は、最初の方法が必要です。uint32_t x[N], y[N], z[N], carry=0; for (int i = 0; i < N; i++) { z[i] = x[i] + y[i] + carry; carry = z[i] < (x[i] | y[i]); }そうしない場合orの値を、1つのオペランドとキャリーは0と1であることオペランドているビットを区別することはできません0xffffffffし、キャリー・ビットが1であること。
マット

14

C / C ++からオーバーフローフラグにアクセスすることはできません。

一部のコンパイラでは、トラップ命令をコードに挿入できます。GCCでは、オプションは-ftrapvです。

実行可能な唯一の移植可能でコンパイラに依存しないことは、自分でオーバーフローをチェックすることです。あなたの例でやったように。

ただし、-ftrapv最新のGCCを使用するx86では何もしないようです。私はそれが古いバージョンの残り物であるか、または他のいくつかのアーキテクチャに固有のものであると思います。追加するたびにコンパイラがINTOオペコードを挿入することを期待していました。残念ながら、これは行いません。


多分それは変化します:-ftrapvはCygwinボックスでGCC 4.3.4を使用して正常に動作するようです。stackoverflow.com/questions/5005379/…に
Nate Kohl

3
お二人とも正しいです。-ftrapvは機能しますが、符号付き整数の場合のみ
ZAB 2013年

14

符号なし整数の場合は、結果が引数の1つより小さいことを確認してください。

unsigned int r, a, b;
r = a + b;
if (r < a)
{
    // Overflow
}

符号付き整数の場合、引数と結果の符号を確認できます。

異なる符号の整数はオーバーフローできません。同じ符号の整数は、結果が異なる符号の場合にのみオーバーフローします。

signed int r, a, b, s;
r = a + b;
s = a>=0;
if (s == (b>=0) && s != (r>=0))
{
    // Overflow
}

まあ、最初の方法は、符号付き整数でも機能しますね。char result = (char)127 + (char)3;-126になります。両方のオペランドよりも小さい。
primfaktor 2012

1
ああ、なるほど、問題は、それが符号付き型に対して未定義であるという事実です。
primfaktor 2012

27
符号付き数値の-1オーバーフローは、未定義の動作を引き起こします(したがって、テストは実際には有用ではありません)。
Voo

1
@primfaktor符号付き整数では機能しません:char((-127)+(-17))=112。符号付き整数では、引数と結果の符号ビットを確認する必要があります
phuclv

3
すでに述べたように、オーバーフローの場合のa + bの未定義の動作のため、符号付き整数の解は機能しません。演算の前に、符号付き整数でオーバーフローをチェックする必要あります。
Marwan Burelle、2016年

11

私は浮動小数点数についても同じ質問に答える必要がありましたが、ビットマスキングとシフトは有望に見えません。私が解決したアプローチは、符号付きおよび符号なしの整数および浮動小数点数に対応しています。中間計算のために昇格するより大きなデータ型がない場合でも機能します。これらすべてのタイプで最も効率的ではありませんが、すべてのタイプで機能するため、使用する価値があります。

署名付きオーバーフローテスト、加算および減算:

  1. タイプの可能な最大値と最小値を表す定数、MAXVALUEおよびMINVALUEを取得します。

  2. オペランドの符号を計算して比較します。

    a。どちらかの値がゼロの場合、加算も減算もオーバーフローすることはありません。残りのテストをスキップします。

    b。符号が逆の場合、加算はオーバーフローできません。残りのテストをスキップします。

    c。符号が同じ場合、減算はオーバーフローできません。残りのテストをスキップします。

  3. MAXVALUEの正のオーバーフローをテストします。

    a。両方の符号が正で、MAXVALUE-A <Bの場合、加算はオーバーフローします。

    b。Bの符号が負で、MAXVALUE-A <-Bの場合、減算はオーバーフローします。

  4. MINVALUEの負のオーバーフローをテストします。

    a。両方の符号が負で、MINVALUE-A> Bの場合、加算はオーバーフローします。

    b。Aの符号が負で、MINVALUE-A> Bの場合、減算はオーバーフローします。

  5. それ以外の場合、オーバーフローはありません。

署名付きオーバーフローテスト、乗算および除算:

  1. タイプの可能な最大値と最小値を表す定数、MAXVALUEおよびMINVALUEを取得します。

  2. オペランドの大きさ(絶対値)を計算して1と比較します。(以下では、AとBはこれらの大きさであり、署名されたオリジナルではないと仮定します。)

    a。いずれかの値がゼロの場合、乗算はオーバーフローできず、除算はゼロまたは無限大になります。

    b。どちらかの値が1の場合、乗算と除算はオーバーフローできません。

    c。1つのオペランドの大きさが1未満で、もう1つのオペランドの大きさが1より大きい場合、乗算はオーバーフローできません。

    d。大きさが両方とも1未満の場合、除算はオーバーフローできません。

  3. MAXVALUEの正のオーバーフローをテストします。

    a。両方のオペランドが1より大きく、MAXVALUE / A <Bの場合、乗算はオーバーフローします。

    b。Bが1未満で、MAXVALUE * B <Aの場合、除算はオーバーフローします。

  4. それ以外の場合、オーバーフローはありません。

注:MINVALUEの最小オーバーフローは、絶対値を取ったため、3で処理されます。ただし、ABS(MINVALUE)> MAXVALUEの場合、いくつかのまれな誤検知が発生します。

アンダーフローのテストも同様ですが、EPSILON(ゼロより大きい正の最小数)が関係しています。


1
少なくともPOSIXシステムでは、SIGFPE信号を浮動小数点のアンダーフロー/オーバーフローに対して有効にできます。
Chris Johnson

浮動小数点に変換して動作しますが、(32ビットマシンでのテストによると)他のソリューションよりもはるかに低速です。
JanKanis 2014年

レビューアーが減算パート2の欠落ケースを検出しました。0-MINVALUEがオーバーフローすることに同意します。したがって、このケースのテストを追加する必要があります。
Paul Chernoch 2015年

<pedantic>整数はアンダーフローしません(=ゼロに近すぎて正確に表現できません)。1.0e-200 / 1.0e200IEEEダブルスを想定した実際のアンダーフローの例です。代わりに、ここでの正しい用語は負のオーバーフローです。</ pedantic>
Arne Vogel

正確に言えば、整数がアンダーフローと見なされない理由は、定義された切り捨て動作のためです。たとえば1/INT_MAX、アンダーフローと見なすこともできますが、言語は単にゼロへの切り捨てを要求します。
Arne Vogel

8

CERTは、「as-if」無限範囲(AIR)整数モデルを使用して、符号付き整数オーバーフロー、符号なし整数の折り返し、および整数の切り捨てを検出および報告する新しいアプローチを開発しました。CERTはモデルを説明するテクニカルレポートを公開し、GCC 4.4.0およびGCC 4.5.0に基づいて機能するプロトタイプを作成しました。

AIR整数モデルは、無限範囲の整数を使用して取得されたものと同等の値を生成するか、ランタイム制約違反を引き起こします。以前の整数モデルとは異なり、AIR整数は正確なトラップを必要としないため、既存の最適化のほとんどを中断または阻害しません。


リンクに役立つものは何も見当たりませんでしたが、それは私がずっと提唱してきたモデルのように思えます。ほとんどの実装が基本的に無料で提供できるという有用なセマンティック保証もサポートしながら、有用な最適化の大部分をサポートします。関数への入力が出力が重要すべてのケースで有効あることをコードが知っているが、出力が重要かどうかは事前にわからない場合、何も影響しない場合にオーバーフローを発生させることができると、すべてのコストでそれらを防ぐ必要があるよりも簡単かつ効率的です。
スーパーキャット

8

別の興味深いツールはIOC:C / C ++用の整数オーバーフローチェッカーです

これはパッチを適用したClangですコンパイラであり、コンパイル時にコードにチェックを追加します。

次のような出力が得られます。

CLANG ARITHMETIC UNDEFINED at <add.c, (9:11)> :
Op: +, Reason : Signed Addition Overflow,
BINARY OPERATION: left (int32): 2147483647 right (int32): 1

1
このパッチは、他のサニタイザの中でclangコードベースにマージされました。私の答えを参照してください。
ZAB 2013年

7

アセンブリ言語を使用するソリューションの別のバリアントは、外部プロシージャです。Linux x64でg ++およびfasmを使用した符号なし整数乗算のこの例。

このプロシージャは、2つの符号なし整数引数(32ビット)を乗算します(amd64の仕様に従って(セクション3.2.3パラメータの受け渡し))。

クラスがINTEGERの場合、シーケンス%rdi、%rsi、%rdx、%rcx、%r8、および%r9の次に使用可能なレジスタが使用されます

(私のコードではediとesiレジスタ))、オーバーフローが発生した場合は結果または0を返します。

format ELF64

section '.text' executable

public u_mul

u_mul:
  MOV eax, edi
  mul esi
  jnc u_mul_ret
  xor eax, eax
u_mul_ret:
ret

テスト:

extern "C" unsigned int u_mul(const unsigned int a, const unsigned int b);

int main() {
    printf("%u\n", u_mul(4000000000,2)); // 0
    printf("%u\n", u_mul(UINT_MAX/2,2)); // OK
    return 0;
}

プログラムをasmオブジェクトファイルにリンクします。私の場合、Qt CreatorLIBS.proファイルに追加します。


5

doubleを使用して結果を計算します。有効数字は15桁です。要件のcのハード上限は10 8  です。最大8桁にすることができます。したがって、範囲内であれば結果は正確になり、それ以外の場合はオーバーフローしません。


5

このマクロを試して、32ビットマシンのオーバーフロービットをテストします(Angel Sinigerskyのソリューションを採用)

#define overflowflag(isOverflow){   \
size_t eflags;                      \
asm ("pushfl ;"                     \
     "pop %%eax"                    \
    : "=a" (eflags));               \
isOverflow = (eflags >> 11) & 1;}

それ以外の場合はオーバーフロービットが上書きされてしまうため、マクロとして定義しました。

次に続くのは、上記のコードセグメントを持つ小さなアプリケーションです。

#include <cstddef>
#include <stdio.h>
#include <iostream>
#include <conio.h>
#if defined( _MSC_VER )
#include <intrin.h>
#include <oskit/x86>
#endif

using namespace std;

#define detectOverflow(isOverflow){     \
size_t eflags;                      \
asm ("pushfl ;"                     \
    "pop %%eax"                     \
    : "=a" (eflags));               \
isOverflow = (eflags >> 11) & 1;}

int main(int argc, char **argv) {

    bool endTest = false;
    bool isOverflow;

    do {
        cout << "Enter two intergers" << endl;
        int x = 0;
        int y = 0;
        cin.clear();
        cin >> x >> y;
        int z = x * y;
        detectOverflow(isOverflow)
        printf("\nThe result is: %d", z);
        if (!isOverflow) {
            std::cout << ": no overflow occured\n" << std::endl;
        } else {
            std::cout << ": overflow occured\n" << std::endl;
        }

        z = x * x * y;
        detectOverflow(isOverflow)
        printf("\nThe result is: %d", z);
        if (!isOverflow) {
            std::cout << ": no overflow ocurred\n" << std::endl;
        } else {
            std::cout << ": overflow occured\n" << std::endl;
        }

        cout << "Do you want to stop? (Enter \"y\" or \"Y)" << endl;

        char c = 0;

        do {
            c = getchar();
        } while ((c == '\n') && (c != EOF));

        if (c == 'y' || c == 'Y') {
            endTest = true;
        }

        do {
            c = getchar();
        } while ((c != '\n') && (c != EOF));

    } while (!endTest);
}

4
すべての32ビットマシンがIntel x86互換であるわけではなく、すべてのコンパイラがgnuアセンブリ構文をサポートしているわけではありません(_MSC_VERMSコンパイルはすべてコードを拒否しますが、テストするコードを投稿するのはおかしいと思います)。
Ben Voigt、2015年


2

C / C ++からオーバーフローフラグにアクセスすることはできません。

私はこれに同意しません。インラインアセンブリ言語を記述して、jo、オーバーフローをトラップするためにx86を使用していると想定し(ジャンプオーバーフロー)命令をます。もちろん、コードは他のアーキテクチャに移植できなくなります。

見てくださいinfo asinfo gcc


8
インラインアセンブラは、C / C ++の機能ではなく、プラットフォームに依存しません。x86では、ブランチの代わりにinto命令を使用できます。
Nils Pipenbrinck 2008年

0

Head Geekの答えを拡張するために、を実行するより速い方法がありaddition_is_safeます。

bool addition_is_safe(unsigned int a, unsigned int b)
{
    unsigned int L_Mask = std::numeric_limits<unsigned int>::max();
    L_Mask >>= 1;
    L_Mask = ~L_Mask;

    a &= L_Mask;
    b &= L_Mask;

    return ( a == 0 || b == 0 );
}

これは64ビットおよび32ビットの符号なし整数が引き続き正常に機能するという点で、マシンアーキテクチャセーフを使用します。基本的に、私は最上位ビット以外のすべてをマスクするマスクを作成します。次に、両方の整数をマスクし、どちらかにそのビットが設定されていない場合、加算は安全です。

これは変更されないため、一部のコンストラクターでマスクを事前に初期化すると、さらに高速になります。


5
これは正しくありません。キャリーは、オーバーフローを引き起こす低い位置からビットをもたらす可能性があります。追加を検討してくださいUINT_MAX + 1。マスキング後a、上位ビットが設定されますが、1ゼロになるため、関数はを返しtrue、追加は安全ですが、直接オーバーフローに向かいます。
豚1​​4

0

mozilla::CheckedInt<T>整数型のオーバーフローチェック済み整数演算を提供しますT(clangおよびgccのコンパイラ組み込み関数を使用可能にします)。コードはMPL 2.0の下IntegerTypeTraits.hAttributes.hあり、Compiler.h他の3つ(、および)のヘッダーのみの非標準ライブラリヘッダーとMozilla固有のアサーションメカニズムに依存しています。コードをインポートする場合は、おそらくアサーションメカニズムを置き換える必要があります。


-1

MSalterの答えは良い考えです。

(精度のために)整数計算が必要であるが、浮動小数点が使用可能な場合は、次のようにすることができます。

uint64_t foo(uint64_t a, uint64_t b) {
    double dc;

    dc = pow(a, b);

    if (dc < UINT_MAX) {
       return (powu64(a, b));
    }
    else {
      // Overflow
    }
}

通常、浮動小数点で計算を繰り返すことは悪い考えですが、この特殊な指数a ^ cの場合は、より効率的です。しかし、テストはである必要が(c * log(a) < max_log)ありますconst double max_log = log(UINT_MAX)
Toby Speight 2017年

-1

x86命令セットには、結果を2つのレジスタに保存する符号なし乗算命令が含まれています。Cからその命令を使用するには、64ビットプログラム(GCC)で次のコードを記述します。

unsigned long checked_imul(unsigned long a, unsigned long b) {
  unsigned __int128 res = (unsigned __int128)a * b;
  if ((unsigned long)(res >> 64))
    printf("overflow in integer multiply");
  return (unsigned long)res;
}

32ビットプログラムの場合、結果を64ビットに、パラメータを32ビットにする必要があります。

別の方法は、コンパイラー依存の組み込み関数を使用してフラグレジスタをチェックすることです。オーバーフロー組み込みのGCCドキュメントは、6.56組み込み関数を使用してオーバーフローチェックで演算を実行するから見つけることができます。


1
符号__uint128付きオーバーフローと負の値の右シフトを回避するには、符号なし128ビットタイプを使用する必要があります。
chqrlie

どのようなものがあり、「コンパイラに依存本能」「オーバーフロー本能」は?あなたが意味するか、「組み込み関数を?リファレンスはありますか?(ここのコメントではなく、適切に回答を編集して返信してください。)
Peter Mortensen

-3
#include <stdio.h>
#include <stdlib.h>

#define MAX 100 

int mltovf(int a, int b)
{
    if (a && b) return abs(a) > MAX/abs(b);
    else return 0;
}

main()
{
    int a, b;

    for (a = 0; a <= MAX; a++)
        for (b = 0; b < MAX; b++) {

        if (mltovf(a, b) != (a*b > MAX)) 
            printf("Bad calculation: a: %d b: %d\n", a, b);

    }
}

-3

それを行うためのクリーンな方法は、すべての演算子(特に+と*)をオーバーライドし、操作を実行する前にオーバーフローをチェックすることです。


6
ただし、組み込み型の演算子はオーバーライドできません。そのためのクラスを作成し、それを使用するようにクライアントコードを書き直す必要があります。
Blaisorblade、2010年

-3

何に使うかによります。符号なしlong(DWORD)の加算または乗算を実行する場合、最良の解決策はULARGE_INTEGERを使用することです。

ULARGE_INTEGERは、2つのDWORDの構造です。完全な値は「QuadPart」としてアクセスでき、上位のDWORDは「HighPart」としてアクセスされ、下位のDWORDは「LowPart」としてアクセスされます。

例えば:

DWORD
My Addition(DWORD Value_A, DWORD Value_B)
{
    ULARGE_INTEGER a, b;

    b.LowPart = Value_A;  // A 32 bit value(up to 32 bit)
    b.HighPart = 0;
    a.LowPart = Value_B;  // A 32 bit value(up to 32 bit)
    a.HighPart = 0;

    a.QuadPart += b.QuadPart;

    // If  a.HighPart
    // Then a.HighPart contains the overflow (carry)

    return (a.LowPart + a.HighPart)

    // Any overflow is stored in a.HighPart (up to 32 bits)

6
残念ながら、これはWindows専用のソリューションです。他のプラットフォームにはありませんULARGE_INTEGER
Mysticial 2013年

-3

移植可能な方法でオーバーフローせずに符号なし乗算を実行するには、以下を使用できます。

... /* begin multiplication */
unsigned multiplicand, multiplier, product, productHalf;
int zeroesMultiplicand, zeroesMultiplier;
zeroesMultiplicand = number_of_leading_zeroes( multiplicand );
zeroesMultiplier   = number_of_leading_zeroes( multiplier );
if( zeroesMultiplicand + zeroesMultiplier <= 30 ) goto overflow;
productHalf = multiplicand * ( c >> 1 );
if( (int)productHalf < 0 ) goto overflow;
product = productHalf * 2;
if( multiplier & 1 ){
   product += multiplicand;
   if( product < multiplicand ) goto overflow;
}
..../* continue code here where "product" is the correct product */
....
overflow: /* put overflow handling code here */

int number_of_leading_zeroes( unsigned value ){
   int ctZeroes;
   if( value == 0 ) return 32;
   ctZeroes = 1;
   if( ( value >> 16 ) == 0 ){ ctZeroes += 16; value = value << 16; }
   if( ( value >> 24 ) == 0 ){ ctZeroes +=  8; value = value <<  8; }
   if( ( value >> 28 ) == 0 ){ ctZeroes +=  4; value = value <<  4; }
   if( ( value >> 30 ) == 0 ){ ctZeroes +=  2; value = value <<  2; }
   ctZeroes -= x >> 31;
   return ctZeroes;
}

-4

オーバーフローをテストする簡単な方法は、現在の値が前の値よりも小さいかどうかをチェックして検証を行うことです。たとえば、2の累乗を出力するループがあるとします。

long lng;
int n;
for (n = 0; n < 34; ++n)
{
   lng = pow (2, n);
   printf ("%li\n", lng);
}

私が説明した方法でオーバーフローチェックを追加すると、この結果になります。

long signed lng, lng_prev = 0;
int n;
for (n = 0; n < 34; ++n)
{
    lng = pow (2, n);
    if (lng <= lng_prev)
    {
        printf ("Overflow: %i\n", n);
        /* Do whatever you do in the event of overflow.  */
    }
    printf ("%li\n", lng);
    lng_prev = lng;
}

これは、符号なしの値だけでなく、正と負の両方の符号付き値に対して機能します。

もちろん、値を増やすのではなく、値を減らすために同様のことをしたい場合は、アンダーフローの動作がオーバーフローの動作と同じ<=である>=と想定して、符号を反転してにします。正直なところ、CPUのオーバーフローフラグにアクセスしない場合と同じくらい移植性があります(インラインアセンブリコードが必要になるため、実装間でコードを移植できなくなります)。


9
符号付き値がオーバーフローする場合、プログラムの動作は未定義です。ラップアラウンドは保証されていません。
David Stone、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.