我用c++写了一个程序来寻找ab = C的所有解,其中a, b和C一起使用所有的数字0-9,只使用一次。程序循环遍历a和b的值,并每次对a、b和ab运行数字计数例程,以检查是否满足数字条件。

但是,当ab超出整数限制时,会产生伪解。我最终使用如下代码来检查这个:

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++中是未定义的行为,因此您必须在不实际引起它的情况下检测它。对于加法前的有符号整型溢出,请参见在C/ c++中检测有符号溢出。


当前回答

有一种方法可以确定一个操作是否可能溢出,使用操作数中最高位的位置和一些基本的二进制数学知识。

对于加法,任何两个操作数的结果(最多)比最大操作数的最高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);
}

对于乘法,任何两个操作数的结果(最多)是操作数的位数之和。例如:

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);
}

类似地,你可以像这样估计a的b次方结果的最大大小:

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;
}

它不是完美的,但它能让你在做运算之前知道是否有两个数会溢出。我不知道它是否会比您建议的方式检查结果更快,因为highestOneBitPosition函数中的循环,但它可能会(特别是如果您事先知道操作数中有多少位)。

其他回答

mozilla::CheckedInt<T>为整数类型T提供溢出检查的整数数学(使用clang和gcc上可用的编译器intrinsic)。该代码是在MPL 2.0下编写的,并且依赖于三个(integertypetrait .h, Attributes.h和Compiler.h)其他仅针对标头的非标准库标头以及mozilla特定的断言机制。如果导入代码,可能需要替换断言机制。

我看到你用的是无符号整数。根据定义,在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 (x != 0 && a > INT_MAX / x) // `a * x` would overflow
if (x != 0 && a < INT_MIN / x) // `a * x` would underflow

对于除法(INT_MIN和-1特殊情况除外),不可能超过INT_MIN或INT_MAX。

这里有一个非常快速的方法来检测溢出,至少是加法,这可能会为乘法、除法和乘方提供线索。

其思想是,正是因为处理器会让值归零,而C/ c++是从任何特定的处理器抽象出来的,你可以:

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

这既确保了如果一个操作数为零,另一个操作数为零,则不会错误地检测到溢出,而且比前面建议的许多NOT/XOR/ and /test操作要快得多。

正如所指出的,这种方法虽然比其他更精细的方法更好,但仍然是可优化的。以下是包含优化的原始代码的修订:

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 uint32_t a = (x >> 16U) * (y & 0xFFFFU);
const uint32_t b = (x & 0xFFFFU) * (y >> 16U);
const bool overflow = ((x >> 16U) * (y >> 16U)) +
    (a >> 16U) + (b >> 16U);
uint32_t value = overflow ? UINT32_MAX : x * y;

这将导致UINT32_MAX溢出,或乘法的结果。在这种情况下,允许对有符号整数进行乘法运算是严格未定义的行为。

值得注意的是,这使用部分Karatsuba方法乘法分解来计算64位乘法的高32位,以检查是否应该设置它们中的任何一个,以了解32位乘法是否溢出。

如果使用c++,你可以把这个转换成一个简洁的小lambda来计算溢出,这样检测器的内部工作就被隐藏了:

uint32_t x, y;
const bool overflow
{
    [](const uint32_t x, const uint32_t y) noexcept -> bool
    {
        const uint32_t a{(x >> 16U) * uint16_t(y)};
        const uint32_t b{uint16_t(x) * (y >> 16U)};
        return ((x >> 16U) * (y >> 16U)) + (a >> 16U) + (b >> 16U);
    }(x, y)
};
uint32_t value{overflow ? UINT32_MAX : x * y};

测试溢出的简单方法是通过检查当前值是否小于前一个值来进行验证。例如,假设你有一个循环输出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溢出标志的情况下所获得的可移植性(这将需要内联汇编代码,使您的代码在实现之间无法移植)。

为了扩展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位无符号整数仍然可以正常工作。基本上,我创建了一个掩码,它将屏蔽除最重要的位外的所有内容。然后,对两个整数进行掩码,如果其中任何一个没有设置该位,则加法是安全的。

如果在某个构造函数中预初始化掩码,这将更快,因为它永远不会改变。