我用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++中检测有符号溢出。


当前回答

I see that a lot of people answered the question about overflow, but I wanted to address his original problem. He said the problem was to find ab=c such that all digits are used without repeating. Ok, that's not what he asked in this post, but I'm still think that it was necessary to study the upper bound of the problem and conclude that he would never need to calculate or detect an overflow (note: I'm not proficient in math so I did this step by step, but the end result was so simple that this might have a simple formula).

重点是问题要求的a b c的上限是98.765.432。不管怎样,先把问题分成琐碎部分和非琐碎部分:

X0 == 1(9、8、7、6、5、4、3、2的所有排列都是解) X1 == x(无解) 0b == 0(不可能解) 1b == 1(无解) Ab, a > 1, b > 1(非平凡)

Now we just need to show that no other solution is possible and only the permutations are valid (and then the code to print them is trivial). We go back to the upper bound. Actually the upper bound is c ≤ 98.765.432. It's the upper bound because it's the largest number with 8 digits (10 digits total minus 1 for each a and b). This upper bound is only for c because the bounds for a and b must be much lower because of the exponential growth, as we can calculate, varying b from 2 to the upper bound:

    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)

其他回答

从C23开始,标准头文件<stdckdint.h>提供了以下三个类函数宏:

bool ckd_add(type1 *result, type2 a, type3 b);
bool ckd_sub(type1 *result, type2 a, type3 b);
bool ckd_mul(type1 *result, type2 a, type3 b);

其中type1, type2和type3是任何整数类型。这些函数分别以任意精度对a和b进行加、减或乘,并将结果存储在*result中。如果结果不能由type1精确表示,则函数返回true("计算已溢出")。(任意精确是一种错觉;计算速度非常快,自20世纪90年代初以来几乎所有可用的硬件都可以在一个或两个指令内完成。)

重写OP的例子:

unsigned long b, c, c_test;
// ...
if (ckd_mul(&c_test, c, b))
{
    // returned non-zero: there has been an overflow
}
else
{
    c = c_test; // returned 0: no overflow
}

C_test包含所有情况下可能溢出的乘法结果。

早在C23之前,GCC 5+和Clang 3.8+就提供了以同样方式工作的内置程序,除了结果指针是最后传递而不是第一个传递:__builtin_add_overflow, __builtin_sub_overflow和__builtin_mul_overflow。这些也适用于小于int的类型。

unsigned long b, c, c_test;
// ...
if (__builtin_mul_overflow(c, b, &c_test))
{
    // returned non-zero: there has been an overflow
}
else
{
    c = c_test; // returned 0: no overflow
}

Clang 3.4+引入了具有固定类型的算术溢出内置函数,但它们的灵活性要低得多,而且Clang 3.8现在已经可用很长时间了。如果你需要使用__builtin_umull_overflow,尽管有更方便的更新选项。

Visual Studio的cl.exe没有直接的等价物。对于无符号加减法,包括<intrin.h>将允许您使用addcarry_uNN和subborrow_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来说已经足够好了),所以这也是一个选择。

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

其思想是,正是因为处理器会让值归零,而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};

这里有一个“不可移植”的解决方案。Intel x86和x64 cpu有所谓的eflags寄存器,在每次整数算术运算后由处理器填充。我将跳过这里的详细描述。相关的标志是“溢出”标志(掩码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);
}

如果操作数相乘而没有溢出,则query_intel_eflags(0x801)将得到0的返回值,即既没有设置进位标志,也没有设置溢出标志。在提供的main()示例代码中,发生溢出,并且两个标志都被设置为1。这个检查并不意味着任何进一步的计算,所以它应该相当快。

警告:GCC在使用-O2编译时会优化掉溢出检查。 选项-Wall会在某些情况下给你一个警告

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

但在这个例子中不是:

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

唯一安全的方法是在溢出发生之前检查溢出,正如CERT论文中所描述的那样,系统地使用这种方法将非常繁琐。

使用-fwrapv编译可以解决这个问题,但会禁用一些优化。

我们迫切需要一个更好的解决方案。我认为编译器应该发出一个警告,默认情况下,优化依赖于溢出没有发生。目前的情况允许编译器优化掉溢出检查,这在我看来是不可接受的。

CERT开发了一种新方法,使用“as-if”无限范围(AIR)整数模型来检测和报告有符号整数溢出、无符号整数包装和整数截断。CERT已经发布了一份描述该模型的技术报告,并生成了一个基于GCC 4.4.0和GCC 4.5.0的工作原型。

AIR整数模型产生的值与使用无限范围整数所获得的值相等,或者导致违反运行时约束。与之前的整数模型不同,AIR整数不需要精确的陷阱,因此不会破坏或抑制大多数现有的优化。