Como detectar estouro de multiplicação de números inteiros não assinados?


618

Eu estava escrevendo um programa em C ++ para encontrar todas as soluções de um b = c , onde a , b e c juntos usar todos os dígitos 0-9 exatamente uma vez. O programa em loop sobre os valores de um e b , e funcionou uma rotina dígitos de contagem de cada vez em um , b e um b para verificar se a condição dígitos estava satisfeito.

No entanto, soluções falsas podem ser geradas quando a b excede o limite inteiro. Acabei verificando isso usando código como:

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

Existe uma maneira melhor de testar o estouro? Eu sei que alguns chips têm um sinalizador interno que é definido quando ocorre um estouro, mas nunca o vi acessado por C ou C ++.


Cuidado que o estouro assinado int é um comportamento indefinido em C e C ++ e, portanto, você deve detectá-lo sem realmente causar isso. Para estouro int assinado antes da adição, consulte Detectando estouro assinado em C / C ++ .


21
Informações que podem ser úteis sobre este assunto: Capítulo 5 de "Codificação segura em C e C ++" da Seacord - http://www.informit.com/content/images/0321335724/samplechapter/seacord_ch05.pdf Classes SafeInt para C ++ - http : //blogs.msdn.com/david_leblanc/archive/2008/09/30/safeint-3-on-codeplex.aspx - http://www.codeplex.com/SafeInt Biblioteca IntSafe para C: - [ blogs.msdn .com / michael_howard / archiv
Michael Burr

3
O Secure Coding do Seacord é um ótimo recurso, mas não use IntegerLib. Veja blog.regehr.org/archives/593 .
JWW

32
A opção do compilador gcc -ftrapvfará com que ele gere um SIGABRT no estouro de número inteiro (assinado). Veja aqui .
Nibot 17/10/12

1
Ele não responde à questão do estouro, mas outra maneira de resolver o problema seria usar uma biblioteca BigNum como o GMP para garantir que você sempre tenha precisão suficiente. Você não precisará se preocupar com estouro se alocar dígitos suficientes antecipadamente.
wrdieter

1
As informações fornecidas por @HeadGeek em sua resposta são basicamente o que eu diria também. No entanto, com uma adição. A maneira como você está detectando o transbordamento para uma multiplicação agora é provavelmente a mais rápida. No ARM, como comentei na resposta do HeadGeek, você pode usar a clzinstrução ou a __clz(unsigned)função para determinar a classificação do número (onde está o seu bit mais alto). Como não tenho certeza se isso está disponível no x86 ou no x64, assumirei que não e dirá que encontrar o bit mais significativo precisará das piores log(sizeof(int)*8)instruções.
Nonsensickle

Respostas:


229

Vejo que você está usando números inteiros não assinados. Por definição, em C (não sei sobre C ++), a aritmética não assinada não transborda ... então, pelo menos para C, seu argumento é discutível :)

Com números inteiros assinados, após o estouro, ocorre um comportamento indefinido (UB) e seu programa pode fazer qualquer coisa (por exemplo: tornar os testes inconclusivos). 

#include <limits.h>

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

Para criar um programa em conformidade, você precisa testar o estouro antes de gerar o referido estouro. O método também pode ser usado com números inteiros não assinados:

// 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 */;

Para a divisão (exceto para o INT_MINe -1caso especial), não há qualquer possibilidade de ir mais INT_MINou INT_MAX.


97
Inteiros não assinados também não estouram estritamente no C ++ (ISO / IEC 14882: 2003 3.9.1.4). Meu uso de 'overflow' na questão foi o significado mais coloquial, destinado a incluir o agrupamento bem definido de tipos não assinados, pois eu estava interessado em ints não assinados representando números inteiros positivos matemáticos, não números inteiros positivos mod 2 ^ 32 (ou 2 ^ 64) A distinção entre transbordamento como desvio do comportamento inteiro de tamanho infinito matemático e transbordamento como comportamento indefinido na linguagem raramente parece ser explicitada.
317 Chris Chris

15
Esse teste não precisa ser x >= 0- x > 0será suficiente (se x == 0, então x + anão pode transbordar por razões óbvias).
caf

2
@pmg, há uma citação de suporte do padrão?
Pacerier 22/09

5
Eu gosto dessa abordagem ... No entanto, tenha cuidado: a detecção de estouro de multiplicação assume um x positivo. Para x == 0, leva a dividir pela detecção zero e, para x negativo, sempre detecta erroneamente o estouro.
Franz D.

4
if ((a < INT_MIN / x))teste é tarde demais. Um if (x == -1) teste é necessário primeiro.
chux - Restabelece Monica

164

Não é uma maneira de determinar se uma operação é susceptível de excesso, usando as posições dos one-bits mais significativos nos operandos e um pouco de conhecimento básico binário matemática.

Além disso, quaisquer dois operandos resultarão em (no máximo) um pouco mais que o bit mais alto do maior operando. Por exemplo:

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

Para multiplicação, quaisquer dois operandos resultarão (no máximo) na soma dos bits dos operandos. Por exemplo:

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

Da mesma forma, você pode estimar o tamanho máximo do resultado da apotência da bseguinte forma:

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

(Substitua o número de bits pelo número inteiro de destino, é claro.)

Não tenho certeza da maneira mais rápida de determinar a posição do bit mais alto de um número, aqui está um método de força bruta:

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

Não é perfeito, mas isso lhe dará uma boa idéia se dois números podem estourar antes de você fazer a operação. Não sei se seria mais rápido do que simplesmente verificar o resultado da maneira que você sugeriu, por causa do loop na highestOneBitPositionfunção, mas poderia ser (especialmente se você soubesse quantos bits havia nos operandos anteriormente).


98
e, claro, você pode renomear highestOneBitPosition para log :)
Oliver Hallam

37
Sim, é a mesma operação que log2, mas isso não seria necessariamente tão óbvio para alguém que não possuía formação matemática.
Head Geek

48
Esse algoritmo não subestima as respostas seguras? 2 ^ 31 + 0 seria detectado como inseguro, já que maximumOneBitPosition (2 ^ 31) = 32. (2 ^ 32 - 1) * 1 seria detectado como inseguro, pois 32 + 1> 32. 1 ^ 100 seria detectado como inseguro desde 1 * 100 > 32.
clahey 15/04

19
de acordo com o seu multiplication_is_safe 0x8000 * 0x10000overflow (as posições dos bits são 16 + 17 = 33, que é > 32 ), embora isso não ocorra porque o 0x8000 * 0x10000 = 0x80000000que obviamente ainda se encaixa em um int de 32 bits não assinado. Este é apenas um dos exemplos de maio para os quais esses códigos não funcionam. 0x8000 * 0x10001, ...
Michi

13
@GT_mh: Seu ponto? Como eu disse, não é perfeito; é uma regra geral que diz definitivamente quando algo está seguro, mas não há como determinar se todos os cálculos seriam aceitáveis ​​sem fazer o cálculo completo. 0x8000 * 0x10000não é "seguro", por esta definição, mesmo que tudo esteja bem.
Chefe Geek

147

O Clang 3.4+ e o GCC 5+ oferecem embutidos aritméticos verificados. Eles oferecem uma solução muito rápida para esse problema, especialmente quando comparados às verificações de segurança de teste de bits.

Para o exemplo da pergunta do OP, funcionaria assim:

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
}

A documentação do Clang não especifica se c_testcontém o resultado do estouro se ocorreu um estouro, mas a documentação do GCC diz que sim. Dado que esses dois gostam de ser __builtincompatíveis, provavelmente é seguro assumir que é assim que Clang também funciona.

Existe um __builtinpara cada operação aritmética que pode exceder (adição, subtração, multiplicação), com variantes assinadas e não assinadas, para tamanhos int, tamanhos longos e tamanhos longos. A sintaxe para o nome é __builtin_[us](operation)(l?l?)_overflow:

  • upara sem sinal ou spara assinado ;
  • operação é uma das add, subou mul;
  • nenhum lsufixo significa que os operandos são ints; um lsignifica long; dois ls significa long long.

Portanto, para uma adição inteira comprada assinada marcada, seria __builtin_saddl_overflow. A lista completa pode ser encontrada na página de documentação do Clang .

GCC 5+ e Clang 3.8+ além de oferecer builtins genéricos que funcionam sem especificar o tipo dos valores: __builtin_add_overflow, __builtin_sub_overflowe __builtin_mul_overflow. Eles também funcionam em tipos menores queint .

Os componentes internos são inferiores ao que é melhor para a plataforma. No x86, eles verificam os sinalizadores de transporte, transbordamento e sinalização.

O cl.exe do Visual Studio não possui equivalentes diretos. Para adições e subtrações não assinadas, inclusive <intrin.h>permitirá que você use addcarry_uNNe subborrow_uNN(onde NN é o número de bits, como addcarry_u8ou subborrow_u64). A assinatura deles é um pouco obtusa:

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 é o sinalizador carry / emprestado na entrada e o valor de retorno é o carry / emprestado na saída. Não parece ter equivalentes para operações ou multiplicações assinadas.

Caso contrário, o Clang for Windows agora está pronto para produção (bom o suficiente para o Chrome), então também pode ser uma opção.


__builtin_sub_overflowdefinitivamente não está no Clang 3.4.
Richard Cook

2
@RichardCook, demorou algum tempo, mas Clang tem os componentes genéricos a partir da versão 3.9.
zneak

@ambre, acho que não.
zneak

4
De acordo com os documentos , __builtin_add_overflowe os amigos já devem estar disponíveis no Clang 3.8.
Lekensteyn 18/04

2
Obrigado. Isso funciona muito bem. Alguma idéia de qual é a função correspondente para o visual c ++? Não consigo encontrá-los.
Mudit Jain 6/18

53

Alguns compiladores fornecem acesso ao sinalizador de estouro de número inteiro na CPU que você pode testar, mas isso não é padrão.

Você também pode testar a possibilidade de estouro antes de executar a multiplicação:

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

11
... ou use numeric_limits <TYPE> :: max ()
Jonas Gulle

20
Não se esqueça de lidar com a = 0 - quebras de divisão então.
Thelema

16
@ Thelema: "Não se esqueça de lidar com a = 0" - e INT_MIN / -1.
JWW

1
E se b == ULONG_MAX / a? Então ainda pode caber, dado que adivide ULONG_MAXsem resíduos.
the swine

Engraçado que, em termos de desempenho, uma multiplicação é bastante rápida em comparação com uma divisão e você está adicionando uma divisão para cada multiplicação. Isso não soa como a solução.
DrumM 30/04

40

Aviso: o GCC pode otimizar uma verificação de estouro ao compilar com -O2. A opção -Wallemitirá um aviso em alguns casos, como

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

mas não neste exemplo:

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

A única maneira segura é verificar o estouro antes que ocorra, conforme descrito no documento CERT , e isso seria incrivelmente tedioso para ser usado sistematicamente.

Compilar com -fwrapvresolve o problema, mas desativa algumas otimizações.

Precisamos desesperadamente de uma solução melhor. Eu acho que o compilador deve emitir um aviso por padrão ao fazer uma otimização que depende do estouro não ocorrer. A situação atual permite que o compilador otimize uma verificação de estouro, o que é inaceitável na minha opinião.


8
Observe que os compiladores só podem fazer isso com tipos inteiros assinados ; estouro é completamente definido para os tipos inteiros não assinados. Ainda assim, sim, é uma armadilha bastante perigosa!
SamB 02/02

1
"Eu acho que o compilador deve emitir um aviso por padrão ao fazer uma otimização que depende do estouro não ocorrer". - então for(int k = 0; k < 5; k++) {...}deveria dar um aviso?
precisa saber é o seguinte

2
@immibis: Por que deveria? Os valores de kpodem ser facilmente determinados em tempo de compilação. O compilador não precisa fazer nenhuma suposição.
MikeMB

2
@immibis: Para citar o acima exposto: "Eu acho que o compilador deve emitir um aviso por padrão ao fazer uma otimização que dependa de estouro não ocorrendo".
MikeMB

1
@MikeMB A otimização em que o compilador não se preocupa em verificar se né inferior a 32, antes de emitir uma instrução shift que usa apenas os 5 bits inferiores de n?
User253751

30

O Clang agora suporta verificações dinâmicas de estouro para números inteiros assinados e não assinados. Veja a opção -fsanitize = integer . Por enquanto, é o único compilador C ++ com verificação de estouro dinâmico totalmente suportada para fins de depuração.


25

Vejo que muitas pessoas responderam à pergunta sobre estouro, mas eu queria resolver o problema original. Ele disse que o problema era encontrar um b = c de modo que todos os dígitos sejam usados ​​sem repetição. Ok, não foi isso que ele pediu neste post, mas ainda acho que era necessário estudar o limite superior do problema e concluir que ele nunca precisaria calcular ou detectar um estouro (nota: não sou proficiente em matemática, eu fiz isso passo a passo, mas o resultado final foi tão simples que isso pode ter uma fórmula simples).

O ponto principal é que o limite superior exigido pelo problema para a, b ou c é 98.765.432. De qualquer forma, começando dividindo o problema nas partes trivial e não trivial:

  • x 0 == 1 (todas as permutações de 9, 8, 7, 6, 5, 4, 3, 2 são soluções)
  • x 1 == x (nenhuma solução é possível)
  • 0 b == 0 (nenhuma solução é possível)
  • 1 b == 1 (nenhuma solução é possível)
  • a b , a> 1, b> 1 (não trivial)

Agora, apenas precisamos mostrar que nenhuma outra solução é possível e apenas as permutações são válidas (e o código para imprimi-las é trivial). Voltamos ao limite superior. Na verdade, o limite superior é c ≤ 98.765.432. É o limite superior porque é o maior número com 8 dígitos (total de 10 dígitos menos 1 para cada aeb). Esse limite superior é apenas para c porque os limites de aeb devem ser muito mais baixos devido ao crescimento exponencial, como podemos calcular, variando de b de 2 ao limite superior:

    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

Observe, por exemplo, a última linha: diz que 1,97 ^ 27 ~ 98M. Assim, por exemplo, 1 ^ 27 == 1 e 2 ^ 27 == 134.217.728 e essa não é uma solução, pois possui 9 dígitos (2> 1,97; portanto, é realmente maior do que deveria ser testado). Como pode ser visto, as combinações disponíveis para testar aeb são realmente pequenas. Para b == 14, precisamos tentar 2 e 3. Para b == 3, começamos em 2 e paramos em 462. Todos os resultados são concedidos como inferiores a ~ 98M.

Agora basta testar todas as combinações acima e procurar as que não repetem nenhum dígito:

    ['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)

Nenhum deles corresponde ao problema (que também pode ser visto pela ausência de '0', '1', ..., '9').

O código de exemplo que resolve ele segue. Observe também que está escrito em Python, não porque ele precisa de números inteiros de precisão arbitrários (o código não calcula nada maior que 98 milhões), mas porque descobrimos que a quantidade de testes é tão pequena que devemos usar uma linguagem de alto nível para faça uso de seus contêineres e bibliotecas integrados (observe também: o código possui 28 linhas).

    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
por que você não está usando 9.876.543.210 como o limite superior?
21418 Tom Roggero

3
Como 2 dígitos devem ser usados ​​para o lado esquerdo da equação.
Hdd #

2
Não que ele faz a diferença, mas o limite superior pode realmente ser tomado como 98765410 como você têm afirmado os valores sobre os LHS são> 1
Paul Childs

24

Aqui está uma solução "não portátil" para a pergunta. As CPUs Intel x86 e x64 possuem o chamado registro EFLAGS , que é preenchido pelo processador após cada operação aritmética inteira. Vou pular uma descrição detalhada aqui. Os sinalizadores relevantes são o sinalizador "Estouro" (máscara 0x800) e o sinalizador "Transporte" (máscara 0x1). Para interpretá-los corretamente, deve-se considerar se os operandos são do tipo assinado ou não assinado.

Aqui está uma maneira prática de verificar os sinalizadores do C / C ++. O código a seguir funcionará no Visual Studio 2005 ou mais recente (32 e 64 bits), bem como no GNU C / C ++ 64 bits.

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

Se os operandos fossem multiplicados sem transbordamento, você obteria um valor de retorno 0 de query_intel_eflags(0x801), ou seja, nem os sinalizadores de transporte nem de transbordamento são definidos. No código de exemplo fornecido de main (), ocorre um estouro e os dois sinalizadores são definidos como 1. Essa verificação não implica cálculos adicionais; portanto, deve ser bastante rápido.


21

Se você tiver um tipo de dados maior que o que você deseja testar (digamos que você adicione um de 32 bits e um de 64 bits), isso detectará se ocorreu um estouro. Meu exemplo é para uma adição de 8 bits. Mas pode ser ampliado.

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

É baseado nos conceitos explicados nesta página: http://www.cs.umd.edu/class/spring2003/cmsc311/Notes/Comb/overflow.html

Para um exemplo de 32 bits, 0xFFtorna 0xFFFFFFFF- 0x80se 0x80000000e torna - se e, finalmente, uint16_ttorna-se um uint64_t.

NOTA : isso captura estouros de adição / subtração de números inteiros e percebi que sua pergunta envolve multiplicação. Nesse caso, a divisão é provavelmente a melhor abordagem. Geralmente, é uma maneira de as callocimplementações garantirem que os parâmetros não excedam à medida que são multiplicados para obter o tamanho final.


O link está quebrado: HTTP 403: Proibido
Peter Mortensen

18

A maneira mais simples é converter seus unsigned longs em unsigned long longs, fazer sua multiplicação e comparar o resultado com 0x100000000LL.

Você provavelmente descobrirá que isso é mais eficiente do que fazer a divisão, como você fez no seu exemplo.

Ah, e funcionará em C e C ++ (como você marcou a pergunta com ambos).


Acabei de dar uma olhada no manual da glibc . Há uma menção de uma interceptação de excesso de número inteiro ( FPE_INTOVF_TRAP) como parte de SIGFPE. Isso seria ideal, além dos bits desagradáveis ​​no manual:

FPE_INTOVF_TRAP Estouro de número inteiro (impossível em um programa C, a menos que você ative o trapping de estouro de maneira específica de hardware).

Um pouco de pena mesmo.


4
Heh ... o que eu não disse foi que estou fazendo essa pergunta em preparação para escrever um programa para resolver um problema com números maiores, no qual já estou usando int longo. Como long long int não está (supostamente) no padrão C ++, fiquei com a versão de 32 bits para evitar confusão.
Chris Johnson

Eu aconselho usar o ULONG_MAXque é mais fácil de digitar e mais portátil que a codificação 0x100000000.
Jw013

24
Isso não funciona quando longe long longé do mesmo tamanho (por exemplo, em muitos compiladores de 64 bits).
precisa saber é o seguinte

Depender de sinais para falar sobre estouros seria muito lento de qualquer maneira.
SamB 31/12/14

@ SamB Somente se for esperado que os estouros sejam frequentes.
precisa saber é o seguinte

18

Aqui está uma maneira muito rápida de detectar o estouro de pelo menos adições, o que pode dar uma vantagem para multiplicação, divisão e potência.

A idéia é que, exatamente porque o processador deixará o valor voltar a zero e que o C / C ++ seja abstraído de qualquer processador específico, você pode:

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

Isso garante que, se um operando é zero e outro não, o estouro não será falsamente detectado e será significativamente mais rápido do que muitas operações de teste NOT / XOR / AND /, conforme sugerido anteriormente.

Como apontado, essa abordagem, embora melhor do que outras formas mais elaboradas, ainda é otimizável. A seguir, é apresentada uma revisão do código original que contém a otimização:

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

Uma maneira mais eficiente e barata de detectar o estouro de multiplicação é:

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

Isso resulta em UINT32_MAX no estouro ou no resultado da multiplicação. É um comportamento estritamente indefinido para permitir que a multiplicação prossiga para números inteiros assinados nesse caso.


Eu discordo devido à teoria da computação. Considere o seguinte: y> x, o valor excede o limite, y é apenas maior que x devido ao bit de sinal que está sendo definido (1 + 255, por exemplo, para caracteres não assinados) testando o valor ex resultaria em x no estouro = false - daí o uso de lógico ou para evitar esse comportamento quebrado. #
DX-MON

O teste funciona para os números que você fornece (x: = 1, y: = 255, tamanho = uint8_t): o valor será 0 (1 + 255) e 0 <1 é verdadeiro. Funciona de fato para todos os pares de números.
Gunther Piez

Hmm, você faz um bom argumento. Eu ainda permaneço do lado da segurança usando o truque ou, embora como qualquer bom compilador o otimize, você está realmente correto para todas as entradas, incluindo números que não transbordam, como "0 + 4", onde o resultado não seria excedente.
DX-MON

4
Se houver um estouro, que x+y>=256e value=x+y-256. Como y<256sempre é verdade, (y-256) é negativo e value < xsempre é verdade. A prova para o caso não transbordante é bastante semelhante.
Gunther Piez

2
@ DX-MON: Seu primeiro método é necessário se você também tiver um bit de carry de um add anterior. 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]); }Se você não fizer oros valores, não poderá distinguir entre um operando e o bit de transporte sendo zero e um bit de operando 0xffffffffe o bit de transporte sendo um.
20515 Matt

14

Você não pode acessar o sinalizador de estouro de C / C ++.

Alguns compiladores permitem inserir instruções de interceptação no código. Em GCC a opção é -ftrapv.

A única coisa portátil e independente do compilador que você pode fazer é verificar os estouros por conta própria. Assim como você fez no seu exemplo.

No entanto, -ftrapvparece não fazer nada no x86 usando o GCC mais recente. Eu acho que é uma sobra de uma versão antiga ou específica para alguma outra arquitetura. Eu esperava que o compilador inserisse um código de operação INTO após cada adição. Infelizmente não faz isso.


Talvez isso varie: -ftrapv parece funcionar bem usando o GCC 4.3.4 em uma caixa Cygwin. Há um exemplo em stackoverflow.com/questions/5005379/…
Nate Kohl

3
Vocês dois estão certos. -ftrapv faz o trabalho, mas apenas para números inteiros assinados
ZAB 31/10

14

Para números inteiros não assinados, verifique se o resultado é menor que um dos argumentos:

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

Para números inteiros assinados, você pode verificar os sinais dos argumentos e do resultado.

Os números inteiros de sinais diferentes não podem transbordar e os números inteiros do mesmo sinal transbordam apenas se o resultado for de um sinal diferente:

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

Bem, o primeiro método também funcionaria para números inteiros assinados, não? char result = (char)127 + (char)3;seria -126; menor que os dois operandos.
primfaktor

1
Ah, entendo, o problema é o fato de não ser definido para tipos assinados.
Primfaktor 13/12/12

27
O excesso de -1 de números assinados resulta em um comportamento indefinido (portanto, o teste é tarde demais para ser realmente útil).
Voo

1
@primfaktor não funciona para int assinado: char ((- 127) + (-17)) = 112. Para int assinado, você deve verificar o bit de sinal dos argumentos e resultado #
phuclv

3
Como já foi dito, a solução para número inteiro assinado não funciona devido ao comportamento indefinido de a + b em caso de estouro. A verificação de estouro com número inteiro assinado deve ser feita antes da operação.
Marwan Burelle

11

Eu precisava responder a mesma pergunta para números de ponto flutuante, onde mascaramento e mudança de bits não parecem promissores. A abordagem que decidi trabalhar para números assinados e não assinados, números inteiros e de ponto flutuante. Funciona mesmo se não houver um tipo de dados maior a ser promovido para cálculos intermediários. Não é o mais eficiente para todos esses tipos, mas como funciona para todos eles, vale a pena usá-lo.

Teste, adição e subtração de estouro assinado:

  1. Obtenha as constantes que representam os maiores e menores valores possíveis para o tipo, MAXVALUE e MINVALUE.

  2. Calcule e compare os sinais dos operandos.

    uma. Se qualquer valor for zero, nem a adição nem a subtração podem exceder. Pule os testes restantes.

    b. Se os sinais são opostos, a adição não pode transbordar. Pule os testes restantes.

    c. Se os sinais forem os mesmos, a subtração não poderá transbordar. Pule os testes restantes.

  3. Teste para estouro positivo de MAXVALUE.

    uma. Se ambos os sinais forem positivos e MAXVALUE - A <B, a adição transbordará.

    b. Se o sinal de B for negativo e MAXVALUE - A <-B, a subtração transbordará.

  4. Teste para estouro negativo de MINVALUE.

    uma. Se ambos os sinais são negativos e MINVALUE - A> B, a adição transbordará.

    b. Se o sinal de A for negativo e MINVALUE - A> B, a subtração transbordará.

  5. Caso contrário, não haverá transbordamento.

Teste de estouro assinado, multiplicação e divisão:

  1. Obtenha as constantes que representam os maiores e menores valores possíveis para o tipo, MAXVALUE e MINVALUE.

  2. Calcule e compare as magnitudes (valores absolutos) dos operandos com um. (Abaixo, suponha que A e B sejam essas magnitudes, não os originais assinados.)

    uma. Se qualquer valor for zero, a multiplicação não poderá exceder e a divisão produzirá zero ou um infinito.

    b. Se um dos valores for um, a multiplicação e a divisão não poderão exceder.

    c. Se a magnitude de um operando estiver abaixo de um e do outro for maior que um, a multiplicação não poderá exceder.

    d. Se as magnitudes são menores que uma, a divisão não pode transbordar.

  3. Teste para estouro positivo de MAXVALUE.

    uma. Se os dois operandos forem maiores que um e MAXVALUE / A <B, a multiplicação será excedida.

    b. Se B for menor que um e MAXVALUE * B <A, a divisão transbordará.

  4. Caso contrário, não haverá transbordamento.

Nota: O estouro mínimo de MINVALUE é tratado por 3, porque assumimos valores absolutos. No entanto, se ABS (MINVALUE)> MAXVALUE, teremos alguns falsos positivos raros.

Os testes para subfluxo são semelhantes, mas envolvem o EPSILON (o menor número positivo maior que zero).


1
Em sistemas POSIX, pelo menos, o sinal SIGFPE pode ser ativado para ponto flutuante sob / estouros.
Chris Johnson

Embora a conversão para ponto flutuante e retorno funcione, é (de acordo com meus testes em uma máquina de 32 bits) muito mais lenta que as outras soluções.
JanKanis

Um revisor detectou um caso ausente para a subtração parte 2. Concordo que 0 - MINVALUE iria estourar. Portanto, testes para este caso devem ser adicionados.
Paul Chernoch

<pedantic> Os números inteiros não ficam abaixo do limite (= ficam muito próximos de zero para serem representados com precisão). 1.0e-200 / 1.0e200seria um exemplo de um underflow real, assumindo que o IEEE dobra. O termo correto aqui, em vez disso, é estouro negativo </ pedante>.
Arne Vogel

Para ser preciso, a razão pela qual os números inteiros não são considerados com subfluxo é por causa do comportamento definido do truncamento, por exemplo, 1/INT_MAXpode ser considerado com subfluxo, mas a linguagem simplesmente exige que o truncamento seja zero.
Arne Vogel

8

A CERT desenvolveu uma nova abordagem para detectar e relatar estouro de número inteiro assinado, agrupamento de número inteiro não assinado e truncamento de número inteiro usando o modelo de número inteiro "como se" infinitamente variado (AIR). O CERT publicou um relatório técnico descrevendo o modelo e produziu um protótipo funcional com base no GCC 4.4.0 e GCC 4.5.0.

O modelo inteiro do AIR produz um valor equivalente a um que seria obtido usando números inteiros infinitamente variados ou resulta em uma violação de restrição de tempo de execução. Diferentemente dos modelos inteiros anteriores, os inteiros do AIR não exigem traps precisos e, consequentemente, não interrompem ou inibem a maioria das otimizações existentes.


Não vi nada útil no link, mas isso parece um modelo que há muito defendo. Ele suporta a grande maioria das otimizações úteis, além de oferecer garantias semânticas úteis que a maioria das implementações pode fornecer sem nenhum custo. Se o código souber que as entradas para uma função serão válidas em todos os casos em que a saída for importante , mas não souber antecipadamente se a saída será importante, poderá permitir que os estouros ocorram nos casos em que não afetarão nada. mais fácil e mais eficiente do que ter que evitá-los a todo custo.
Supercat

8

Outra ferramenta interessante é o IOC: um verificador de estouro inteiro para C / C ++ .

Este é um Clang remendado compilador , que adiciona verificações ao código no momento da compilação.

Você obtém saída assim:

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

1
Agora, este patch foi mesclado para codificar a base de código entre outros desinfetantes, veja minha resposta.
ZAB

7

Outra variante de uma solução, usando linguagem assembly, é um procedimento externo. Este exemplo para multiplicação de números inteiros não assinados usando g ++ e fasm no Linux x64.

Este procedimento multiplica dois argumentos inteiros não assinados (32 bits) (de acordo com a especificação para amd64 (seção 3.2.3 Passagem de parâmetro ).

Se a classe for INTEGER, o próximo registro disponível da sequência% rdi,% rsi,% rdx,% rcx,% r8 e% r9 será usado

(edi e esi registra no meu código)) e retorna o resultado ou 0 se um estouro ocorreu.

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

Teste:

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

Vincule o programa ao arquivo de objeto asm. No meu caso, no Qt Creator , adicione-o LIBSem um arquivo .pro.


5

Calcule os resultados com dobras. Eles têm 15 dígitos significativos. Sua exigência tem um limite superior rígido em c de 10 8  - ele pode ter no máximo 8 dígitos. Portanto, o resultado será preciso se estiver dentro do alcance e, caso contrário, não excederá o limite.


5

Experimente esta macro para testar o bit de estouro de máquinas de 32 bits (adaptou a solução de Angel Sinigersky)

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

Eu o defini como uma macro porque, caso contrário, o bit de estouro seria substituído.

A seguir é uma pequena aplicação com o segmento de código acima:

#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
Nem todas as máquinas de 32 bits são compatíveis com Intel x86, e nem todos os compiladores suportam sintaxe de assembly gnu (acho engraçado que você publique códigos que testam, _MSC_VERembora as compilações da Microsoft rejeitem o código).
Ben Voigt


2

Você não pode acessar o sinalizador de estouro de C / C ++.

Eu não concordo com isso. Você pode escrever alguma linguagem assembly embutida e usar umjo instrução (jump overflow) assumindo que você esteja no x86 para interceptar o estouro. Obviamente, seu código não seria mais portátil para outras arquiteturas.

Olhe para info ase info gcc.


8
O assembler em linha não é um recurso C / C ++ e independente de plataforma. No x86, você pode usar a instrução into istead of branches btw.
Nils Pipenbrinck 13/10/08

0

Para expandir a resposta do Head Geek, existe uma maneira mais rápida de fazer o 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 );
}

Isso usa arquitetura segura da máquina, pois números inteiros não assinados de 64 e 32 bits ainda funcionarão bem. Basicamente, eu crio uma máscara que mascarará tudo, exceto o bit mais significativo. Então, mascarei os dois números inteiros e, se um deles não tiver esse bit definido, a adição será segura.

Isso seria ainda mais rápido se você pré-inicializar a máscara em algum construtor, pois ela nunca muda.


5
Isso não está correto. O transporte pode trazer bits de posições mais baixas que causarão estouro. Considere adicionar UINT_MAX + 1. Após o mascaramento, ao bit alto será definido, mas 1se tornará zero e, portanto, a função retornará true, a adição é segura - mas você está diretamente direcionado para o estouro.
the swine

0

mozilla::CheckedInt<T>fornece matemática de número inteiro verificado pelo estouro para o tipo inteiro T(usando intrínsecas do compilador no clang e no gcc, conforme disponível). O código está no MPL 2.0 e depende de três ( IntegerTypeTraits.h, Attributes.he Compiler.h) outros cabeçalhos de biblioteca não padrão apenas de cabeçalho, além de mecanismos de asserção específicos para Mozilla . Você provavelmente deseja substituir o mecanismo de asserção se importar o código.


-1

A resposta do MSalter é uma boa ideia.

Se o cálculo inteiro for necessário (para precisão), mas o ponto flutuante estiver disponível, você poderá fazer algo como:

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

Normalmente, eu diria que repetir o cálculo em ponto flutuante é uma má idéia, mas para esse caso específico de exponenciação a ^ c, pode muito bem ser mais eficiente. Mas o teste deve ser (c * log(a) < max_log), ondeconst double max_log = log(UINT_MAX)
Toby Speight

-1

O conjunto de instruções x86 inclui uma instrução de multiplicação não assinada que armazena o resultado em dois registros. Para usar essa instrução de C, pode-se escrever o seguinte código em um programa de 64 bits (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;
}

Para um programa de 32 bits, é necessário tornar o resultado em 64 bits e os parâmetros em 32 bits.

Uma alternativa é usar o intrínseco dependente do compilador para verificar o registro do sinalizador. A documentação do GCC para transbordamento intrínseco pode ser encontrada em 6.56 Funções internas para executar aritmética com verificação de estouro .


1
Você deve usar o tipo de 128 bits __uint128não assinado para evitar estouros assinados e mudar à direita um valor negativo.
chqrlie

O que são "instintos dependentes do compilador" e "instintos de estouro" ? Você quer dizer " funções intrínsecas " ? Você tem uma referência? (Por favor, responda por editar a sua resposta , não aqui nos comentários (conforme o caso).)
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

Uma maneira limpa de fazer isso seria substituir todos os operadores (+ e * em particular) e verificar se há um estouro antes de executar as operações.


6
Exceto que você não pode substituir operadores por tipos internos. Você precisaria escrever uma classe para isso e reescrever o código do cliente para usá-lo.
Blaisorblade

-3

Depende do que você usa. Realizando adição ou multiplicação longa não assinada (DWORD), a melhor solução é usar ULARGE_INTEGER.

ULARGE_INTEGER é uma estrutura de dois DWORDs. O valor total pode ser acessado como "QuadPart", enquanto o DWORD alto é acessado como "HighPart" e o DWORD baixo é acessado como "LowPart".

Por exemplo:

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
Infelizmente, esta é uma solução apenas para Windows. Outras plataformas não têm ULARGE_INTEGER.
Mysticial

-3

Para executar uma multiplicação sem sinal sem transbordar de maneira portátil, o seguinte pode ser usado:

... /* 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

A maneira simples de testar o estouro é fazer a validação, verificando se o valor atual é menor que o valor anterior. Por exemplo, suponha que você tenha um loop para imprimir os poderes de 2:

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

A adição de verificação de estouro da maneira que descrevi resulta em:

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

Ele funciona para valores não assinados, bem como valores assinados positivos e negativos.

Obviamente, se você quisesse fazer algo semelhante para diminuir valores em vez de aumentar valores, você iria virar o <=sinal para fazê-lo >=, assumindo que o comportamento do underflow é o mesmo que o comportamento do overflow. Com toda a honestidade, isso é tão portátil quanto você obtém sem acesso ao sinalizador de estouro de uma CPU (e isso exigiria código de montagem em linha, tornando seu código não portátil em implementações de qualquer maneira).


9
Se um valor assinado exceder o limite, o comportamento do seu programa é indefinido. Não é garantido que se enrole.
David Stone
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.