Як я можу виявити неподписане ціле множинне переповнення?


618

Я писав програму на C ++, щоб знайти всі рішення a b = c , де a , b і c разом використовують усі цифри 0-9 рівно один раз. Програма накинута значення і Ь , і він побіг цифру підрахунок рутинного кожен раз на більш , б і в б , щоб перевірити , якщо цифри умова була виконана.

Тим НЕ менше, помилкові рішення можуть бути отримані при б перетікає межа цілого. Я перевірив це за допомогою коду типу:

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
Інформація, яка може бути корисною з цього приводу: Розділ 5 "Безпечне кодування в C і C ++" від Seacord - http://www.informit.com/content/images/0321335724/samplechapter/seacord_ch05.pdf Безпечні класи для C ++ - http : //blogs.msdn.com/david_leblanc/archive/2008/09/30/safeint-3-on-codeplex.aspx - http://www.codeplex.com/SafeInt IntSafe library for C: - [ blogs.msdn .com / michael_howard / archiv
Майкл Берр

3
Безпечне кодування Seacord - це чудовий ресурс, але не використовуйте IntegerLib. Див. Blog.regehr.org/archives/593 .
jww

32
Параметр компілятора gcc -ftrapvпризведе до того, що він генерує SIGABRT на (підписане) ціле число. Дивіться тут .
nibot

1
Це не відповідає на питання про переповнення, але іншим способом вирішити проблему було б використання бібліотеки BigNum на зразок GMP, щоб гарантувати вам завжди достатню точність. Вам не доведеться турбуватися про переповнення, якщо ви виділите достатньо цифр спереду.
wrdieter

1
Інформація, яку подав @HeadGeek у своїй відповіді, це майже все, що я б сказав також. Однак з одним доповненням. Те, як ви зараз виявляєте перевитрату для множення, мабуть, найшвидший. На ARM, як я коментував у відповіді HeadGeek, ви можете використовувати clzінструкцію або __clz(unsigned)функцію, щоб визначити ранг числа (де його найвищий біт). Оскільки я не впевнений, чи це доступно на x86 або x64, я припускаю, що це не так, і я скажу, що пошук найбільш значущого біта займе найгірші log(sizeof(int)*8)інструкції.
нечутливий

Відповіді:


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 == 0, то x + aне може переповнюватися з зрозумілих причин).
кафе

2
@pmg, чи є підтримуюча цитата зі стандарту?
Pacerier

5
Мені подобається такий підхід ... Однак будьте уважні: виявлення переповнення множення передбачає позитивний х. Для x == 0 це призводить до поділу на нульове виявлення, а для від'ємного x - це завжди помилково виявляє переповнення.
Франц Д.

4
if ((a < INT_MIN / x))тест занадто пізно Спочатку if (x == -1) потрібен тест.
chux

164

Там є спосіб визначити , є чи операція, ймовірно, переповнення, використовуючи позиції найбільш значущих одного біта в операндах і трохи основний двійковій-математичні знання.

Крім того, будь-які два операнди приведуть (щонайбільше) на один біт більше, ніж найбільший один біт операнда. Наприклад:

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

(Замініть кількість бітів для цільового цілого числа, звичайно.)

Я не впевнений у найшвидшому способі визначення позиції найвищого однорозрядного ряду, ось метод грубої сили:

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

Це не ідеально, але це дасть вам хороше уявлення про те, чи можуть якісь два числа переповнитись перед операцією. Я не знаю, чи було б швидше, ніж просто перевірити результат так, як ви запропонували, через цикл у highestOneBitPositionфункції, але це може бути (особливо, якби ви раніше знали, скільки бітів було в операндах).


98
і звичайно, ви можете перейменувати найвищийOneBitPosition у журнал :)
Олівер Халлам,

37
Так, це та сама операція, що і log2, але це не обов'язково буде настільки очевидним для того, хто не мав математичного походження.
Head Geek

48
Чи не цей алгоритм недооцінює безпечні відповіді? 2 ^ 31 + 0 буде визначати як небезпечний, оскільки найвищийOneBitPosition (2 ^ 31) = 32. (2 ^ 32 - 1) * 1 виявив би як небезпечний з 32 + 1> 32. 1 ^ 100 виявив би як небезпечний з 1 * 100 > 32.
clahey

19
відповідно до вашого multiplication_is_safe 0x8000 * 0x10000переповнення (бітові позиції становлять 16 + 17 = 33, що > 32 ), хоча це не так, 0x8000 * 0x10000 = 0x80000000що, очевидно, все ще вписується в непідписаний 32-бітовий int. Це лише один із можливих прикладів, для яких цей код не працює. 0x8000 * 0x10001, ...
Мічі

13
@GT_mh: Ваша думка? Як я вже сказав, це не ідеально; це правило емпіричного , що буде остаточно сказати , коли що - то є безпечним, але немає ніякого способу , щоб визначити , чи буде кожен розрахунок буде в порядку , не роблячи повний розрахунок. 0x8000 * 0x10000не є "безпечним" за цим визначенням, навіть якщо це виявляється нормально.
Head Geek

147

Clang 3.4+ та GCC 5+ пропонують перевірені арифметичні вбудовані елементи. Вони пропонують дуже швидко вирішити цю проблему, особливо в порівнянні з перевіркими безпеки бітових тестів.

Для прикладу в питанні ОП, це спрацювало б так:

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 говорить, що це так. Зважаючи на те, що ці двоє люблять бути __builtinсумісними, можливо, можна з упевненістю припустити, що так працює і Кланг.

Існує __builtinдля кожної арифметичної операції, яка може переповнюватись (додавання, віднімання, множення), з підписаними та непідписаними варіантами, для розмірів int, довгих і довгих величин. Синтаксис імені __builtin_[us](operation)(l?l?)_overflow:

  • uдля неподписаних або sдля підписаних ;
  • операція є однією з add, subабо mul;
  • жоден lсуфікс не означає, що операнди є ints; один lзасіб long; два ls середнє long long.

Отже, для перевіреного підписаного довгого цілого додавання це було б __builtin_saddl_overflow. Повний список можна знайти на сторінці документації Clang .

GCC 5+ і Clang 3.8+ додатково пропонують загальні внутрішні команди , які працюють без вказівки типу значень: __builtin_add_overflow, __builtin_sub_overflowі __builtin_mul_overflow. Вони також працюють на типи менше int.

Вбудовані нижче, ніж найкраще для платформи. На x86 вони перевіряють прапорці, переповнення та підписують.

У cl.exe Visual Studio немає прямих еквівалентів. Для безпідписаних додавань і віднімань, у тому числі <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 для Windows зараз готовий до виробництва (достатньо хороший для Chrome), тож це може бути і варіантом.


__builtin_sub_overflowточно не в Clang 3.4.
Річард Кук

2
@RichardCook, це зайняло деякий час, але Clang має загальні вбудовані версії 3.9.
zneak

@tambre, я не думаю, що є.
zneak

4
Згідно з документами , __builtin_add_overflowі друзі повинні бути вже доступні на Clang 3.8.
Лекенштейн

2
Дякую. Це чудово працює. Будь-яка ідея, що відповідає функції візуального c ++? Не можу їх знайти.
Mudit Jain

53

Деякі компілятори надають доступ до цілого прапора переповнення в процесорі, який ви можете потім протестувати, але це не стандартно.

Ви також можете перевірити можливість переповнення перед виконанням множення:

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

11
... або використовувати numeric_limits <TYPE> :: max ()
Jonas Gulle

20
Не забудьте обробити а = 0 - перерви ділення.
Телема

16
@Thelema: "Не забудьте обробити a = 0" - і INT_MIN / -1.
jww

1
Що робити, якщо b == ULONG_MAX / a? Тоді він ще може поміститися, враховуючи, що aділиться ULONG_MAXбез залишку.
свиня

Смішно, що, ефективніше, множення досить швидко порівняно з діленням, і ви додаєте ділення для кожного множення. Це не схоже на рішення.
DrumM

40

Попередження: GCC може оптимізувати перевірку переповнення при компілюванні -O2. Цей параметр -Wallнабере попередження у деяких випадках

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

але не в цьому прикладі:

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

Єдиний безпечний спосіб - це перевірити наявність переповнення до його виникнення, як це описано в документі CERT , і це було б неймовірно стомлювальним систематичним використанням.

Складання з -fwrapv вирішує проблему, але вимикає деякі оптимізації.

Ми відчайдушно потребуємо кращого рішення. Я думаю, що компілятор повинен видавати попередження за замовчуванням, коли робить оптимізацію, яка покладається на те, що переповнення не відбувається. Нинішня ситуація дозволяє компілятору оптимізувати перевірку переповнення, що на мою думку є неприйнятним.


8
Зауважте, що компілятори можуть робити це лише з підписаними цілими типами; переповнення повністю визначено для цілих непідписаних типів. Все-таки так, це досить небезпечна пастка!
СамБ

1
"Я думаю, що компілятор повинен видавати попередження за замовчуванням, коли робить оптимізацію, яка покладається на те, що переповнення не відбувається." - значить, for(int k = 0; k < 5; k++) {...}повинно бути попередження?
користувач253751

2
@immibis: Навіщо це робити? Значення kлегко можна визначити під час компіляції. Компілятор не повинен робити жодних припущень.
MikeMB

2
@immibis: Цитувати вище: "Я думаю, що компілятор повинен видати попередження за замовчуванням, коли робить оптимізацію, яка покладається на те, що переповнення не відбувається".
MikeMB

1
@MikeMB Оптимізація, коли компілятор не намагається перевірити, що nце менше 32, перед тим, як випустити інструкцію shift, яка використовує лише 5 нижніх біт n?
користувач253751

30

Clang тепер підтримує динамічні перевірки на переповнення як підписаних, так і непідписаних цілих чисел. Див. Перемикач -fsanitize = ціле число . Наразі це єдиний компілятор C ++ з повністю підтримуваною динамічною перевіркою переповнення для цілей налагодження.


25

Я бачу, що багато людей відповіли на питання про переповнення, але я хотів вирішити його первісну проблему. Він сказав, що проблема полягає в тому, щоб знайти б = 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 цифр загалом мінус 1 для кожного a і b). Ця верхня межа є лише для 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 ~ 98М. Наприклад, 1 ^ 27 == 1 і 2 ^ 27 == 134,217,728, і це не є рішенням, оскільки він має 9 цифр (2> 1,97, тож він фактично більший, ніж те, що слід перевірити). Як видно, комбінацій, доступних для тестування a і b, дійсно мало. Для b == 14 нам потрібно спробувати 2 і 3. Для b == 3 ми почнемо з 2 і зупинимось на 462. Усі результати вважаються меншими, ніж ~ 98М.

Тепер просто протестуйте всі комбінації, наведені вище, і шукайте ті, які не повторюють жодної цифри:

    ['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, не тому, що для нього потрібні довільні точні цілі числа (код не обчислює нічого більшого, ніж 98 мільйонів), а тому, що ми з’ясували, що кількість тестів настільки мала, що нам слід використовувати мову високого рівня, щоб використовувати його вбудовані контейнери та бібліотеки (також зверніть увагу: код має 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 як верхню межу?
Том Роджеро

3
Оскільки для лівої частини рівняння потрібно використовувати дві цифри.
hdante

2
Не те, що це має значення, але верхню межу насправді можна сприймати як 98765410, оскільки ви заявили, що значення на LHS становлять> 1
Пол Чайлдс

24

Ось "непереносне" рішення питання. Процесори Intel x86 та x64 мають так званий EFLAGS-регістр , який заповнюється процесором після кожної цілочисельної арифметичної операції. Я пропущу тут детальний опис. Відповідними прапорами є прапор "Переповнення" (маска 0x800) та прапор "Нести" (маска 0х1). Щоб правильно їх інтерпретувати, слід враховувати, чи операнди мають підписаний чи непідписаний тип.

Ось практичний спосіб перевірити прапори 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-бітового прикладу а, 0xFFстає 0xFFFFFFFFі 0x80стає 0x80000000і , нарешті , uint16_tстає uint64_t.

ПРИМІТКА : це ловить цілі числа додавання / віднімання переповнення, і я зрозумів, що ваше запитання передбачає множення. У такому випадку поділ, ймовірно, є найкращим підходом. Це звичайно спосіб, який callocреалізує переконання, що параметри не переповнюються під час їх множення для отримання остаточного розміру.


Посилання розірвано: HTTP 403: Заборонено
Пітер Мортенсен

18

Найпростіший спосіб - перетворити ваш unsigned longs в unsigned long longs, зробити множення і порівняти результат на 0x100000000LL.

Напевно, ви побачите, що це ефективніше, ніж робити поділ, як ви це зробили у своєму прикладі.

О, і він працюватиме як на C, так і на C ++ (як ви позначели питання обома).


Щойно дивився на посібник з glibc . Там згадується ціла пастка переповнення ( FPE_INTOVF_TRAP) як частина SIGFPE. Це було б ідеально, окрім бридких шматочків у посібнику:

FPE_INTOVF_TRAP Переповнення цілого числа (неможливо в програмі C, якщо ви не ввімкнули захоплення переповнення в залежності від обладнання).

Трохи сором справді.


4
Хе ... те, що я не сказав, це те, що я задаю це питання під час підготовки до написання програми для вирішення проблеми з більшими числами, в якій я вже використовую довгий довгий int. Оскільки довгий довгий int не входить (нібито) у стандарт C ++, я дотримувався 32-бітної версії, щоб уникнути плутанини.
Кріс Джонсон

Я б радив використовувати те, ULONG_MAXщо простіше набрати та більш портативно, ніж жорстке кодування 0x100000000.
jw013

24
Це не працює , якщо longі long longмають однаковий розмір (наприклад , на багатьох 64-бітових компіляторів).
interjay

Покладатися на сигнали, щоб розповісти про переливи, все одно було б дуже повільним.
СамБ

@SamB Тільки якщо очікуються часті переливи.
користувач253751

17

Ось дійсно швидкий спосіб виявити переповнення принаймні доповнень, який може призвести до множення, поділу та потужності.

Ідея полягає в тому, що саме тому, що процесор просто дозволить повернути значення до нуля, а C / C ++ - абстрагуватися від будь-якого конкретного процесора, ви можете:

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

Це гарантує, що якщо один операнд дорівнює нулю, а один - ні, тоді переповнення не буде виявлено помилково і буде значно швидше, ніж багато операцій 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, значення переповнює, y лише більше x через встановленого біта знаків (1 + 255, наприклад, для непідписаних символів) значення тестування і x призведе до in overflow = false - значить, використання логічного або запобігання такої порушеної поведінки ..
DX-MON

Тест працює для цифр, які ви даєте (x: = 1, y: = 255, size = uint8_t): значення буде 0 (1 + 255), а 0 <1 - істинним. Це дійсно працює для кожної пари номерів.
Гюнтер П'єз

Хм, ти добре зазначаєш. Я все ще тримаюся на стороні безпеки, використовуючи або трюк, хоча як і будь-який хороший компілятор оптимізував би його постачальника, ви справді правильні для всіх входів, включаючи непереповнені числа, такі як "0 + 4", де результат не був би переповненим.
DX-MON

4
Якщо є перелив, то x+y>=256і value=x+y-256. Оскільки y<256завжди відповідає дійсності, (y-256) є негативним і так value < xзавжди. Доказ для випадку, що не переповнюється, досить схожий.
Гюнтер П'єз

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значень, ви не зможете розрізнити один операнд і біт носія, що дорівнює нулю, і один операнд, 0xffffffffа переносний біт - один.
Метт

14

Ви не можете отримати доступ до прапора переповнення з C / C ++.

Деякі компілятори дозволяють вставляти в код інструкції з пастки. На GCC варіант є -ftrapv.

Єдине, що ви можете зробити від портативного та компілятора - це перевірити наявність переливів самостійно. Так само, як ви зробили у своєму прикладі.

Однак, -ftrapvсхоже, нічого не робиться на x86, використовуючи останні GCC. Я думаю, що це залишок від старої версії або специфічний для якоїсь іншої архітектури. Я очікував, що компілятор вставить код INTO після кожного додавання. На жаль, це не робить.


Можливо, він різниться: начебто -ftrapv працює добре за допомогою GCC 4.3.4 на скриньці Cygwin. Ось приклад на stackoverflow.com/questions/5005379/…
Nate Kohl

3
Ви обоє праві. -ftrapv виконайте роботу, але тільки для підписаних цілих чисел
ZAB

14

Для непідписаних цілих чисел просто перевірте, чи результат менший, ніж один із аргументів:

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

1
О, я бачу, проблема полягає в тому, що вона не визначена для підписаних типів.
primfaktor

27
-1 переповнення підписаних цифр призводить до невизначеної поведінки (отже, тест занадто пізно, щоб бути фактично корисним).
Voo

1
@primfaktor це не працює для підписаного int: char ((- 127) + (-17)) = 112. Для підписаного int ви повинні перевірити біт знаків аргументів та результат
phuclv

3
Як уже було сказано, рішення для підписаного цілого числа не працює через невизначену поведінку а + b у разі переповнення. Перевірка на переповнення підписаним цілим числом повинна бути виконана перед операцією.
Marwan Burelle

11

Мені потрібно було відповісти на це саме запитання для чисел з плаваючою комою, де бітове маскування та зсув не виглядає перспективно. Я підходив до роботи для підписаних і неподписаних, цілих чисел із плаваючою комою. Він працює, навіть якщо немає більшого типу даних для проміжних обчислень. Він не найефективніший для всіх цих типів, але оскільки він працює для всіх, його варто використовувати.

Підписаний тест на переповнення, додавання та віднімання:

  1. Отримайте константи, які представляють найбільші та найменші можливі значення для типу, MAXVALUE та MINVALUE.

  2. Обчисліть та порівняйте ознаки операндів.

    а. Якщо будь-яке значення дорівнює нулю, ні додавання, ні віднімання не можуть переповнювати. Пропустіть решту тестів.

    б. Якщо знаки протилежні, то додавання не може переповнюватись. Пропустіть решту тестів.

    c. Якщо знаки однакові, віднімання не може переповнюватися. Пропустіть решту тестів.

  3. Тест на позитивний перелив MAXVALUE.

    а. Якщо обидва ознаки позитивні, а МАКСАЛЬНІ - А <В, то додавання переповнюється.

    б. Якщо знак B негативний, а MAXVALUE - A <-B, віднімання буде переповнене.

  4. Тест на негативний перелив MINVALUE.

    а. Якщо обидва знаки негативні, а MINVALUE - A> B, додавання буде переповнене.

    б. Якщо знак A негативний, а MINVALUE - A> B, віднімання буде переповнене.

  5. В іншому випадку переливу немає.

Підписаний тест на переповнення, множення та ділення:

  1. Отримайте константи, які представляють найбільші та найменші можливі значення для типу, MAXVALUE та MINVALUE.

  2. Обчисліть та порівняйте величини (абсолютні значення) операндів до одиниці. (Нижче, припустимо, A і B - це величини, а не підписані оригінали.)

    а. Якщо будь-яке значення дорівнює нулю, множення не може переповнитися, і ділення дасть нуль або нескінченність.

    б. Якщо будь-яке значення одне, множення і ділення не можуть переповнюватись.

    c. Якщо величина одного операнда нижче одного, а іншого більше, ніж одиниця, множення не може переповнюватися.

    г. Якщо величини обидві менше, ніж одна, поділ не може переповнюватися.

  3. Тест на позитивний перелив MAXVALUE.

    а. Якщо обидва операнди більше одного і MAXVALUE / A <B, множення буде переповнене.

    б. Якщо B менше одного, а MAXVALUE * B <A, то поділ буде переповнений.

  4. В іншому випадку переливу немає.

Примітка: Мінімальне переповнення MINVALUE обробляється на 3, оскільки ми взяли абсолютні значення. Однак якщо ABS (MINVALUE)> MAXVALUE, у нас будуть деякі рідкісні помилкові позитиви.

Тести на підтік схожі, але включають EPSILON (найменше додатне число, що перевищує нуль).


1
Принаймні, в системах POSIX сигнал SIGFPE може бути включений для плаваючої точки під / переповненням.
Кріс Джонсон

Хоча перетворення на плаваючу крапку і назад працює, воно (за моїм тестуванням на 32-бітній машині) набагато повільніше, ніж інші рішення.
ЯнКаніс

Рецензент виявив відсутній випадок віднімання частини 2. Я згоден, що 0 - MINVALUE переповниться. Тож слід додати тестування на цей випадок.
Павло Чорноч

<pedantic> Цілі особи не переливаються (= стають занадто близькими до нуля, щоб бути представленими з будь-якою точністю). 1.0e-200 / 1.0e200може бути прикладом фактичного заниження, припускаючи, що IEEE удвічі. Натомість тут правильний термін - негативне переповнення. </pedantic>
Арне Фогель

Якщо бути точним, причина, через яку цілі числа не вважаються перевантаженими, пов’язана з визначеною поведінкою усічення, наприклад, 1/INT_MAXцілком можна вважати підточним, але мова просто призначає усічення до нуля.
Арне Фогель

8

CERT розробив новий підхід до виявлення та повідомлення про переповнення підписаних цілих чисел, непідписане ціле числення та ціле укорочення за допомогою цілої моделі "as-if" безмежно діапазону (AIR). CERT опублікував технічний звіт описує модель та випустив робочий прототип на основі GCC 4.4.0 та GCC 4.5.0.

Ціла модель AIR або створює значення, еквівалентне такому, яке було б отримане з використанням нескінченних цілих чисел, або призводить до порушення обмеження часу виконання. На відміну від попередніх цілочисельних моделей, цілі числа AIR не потребують точних пасток, а отже, не порушують та не гальмують більшість існуючих оптимізацій.


Я не бачив нічого корисного за посиланням, але це звучить як модель, яку я давно обстоюю. Він підтримує переважну більшість корисних оптимізацій, а також підтримує корисні семантичні гарантії, які більшість реалізацій можуть надавати по суті безкоштовно. Якщо код знає, що входи до функції будуть дійсними у всіх випадках, коли вихід має значення , але заздалегідь не знає, чи буде вихід важливим, будучи спроможним не допустити переповнення в тих випадках, коли вони ні на що не вплинуть. простіше та ефективніше, ніж потрібно запобігти їх будь-якою ціною.
Supercat

8

Ще один цікавий інструмент - IOC: перевірка переповнення цілої кількості для C / C ++ .

Це виправлений Кланг компілятор , який додає чеки до коду під час компіляції.

Ви отримуєте такий вигляд:

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

1
Цей патч тепер об'єднаний у базу даних коду між іншими дезінфікуючими засобами, дивіться мою відповідь.
ЗАБ

7

Інший варіант рішення, використовуючи мову складання, - це зовнішня процедура. Цей приклад для безпідписаного цілого множення з використанням g ++ та fasm під Linux x64.

Ця процедура множує два непідписані цілі аргументи (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 Creator додайте його LIBSу файл .pro.


5

Результати обчисліть подвійними. Вони мають 15 значущих цифр. Ваша вимога має жорстку верхню межу на c 10 8  - вона може мати не більше 8 цифр. Отже, результат буде точним, якщо він знаходиться в межах, і в іншому випадку він не переповниться.


5

Спробуйте цей макрос, щоб перевірити біт переповнення 32-розрядних машин (адаптовано рішення Ангела Синігерського)

#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_VERхоча компіляція MS все відкидає код).
Бен Войгт

2

Переповнення цілого переповнення в C вказує на рішення, більш загальне, ніж те, що обговорювалося CERT (воно більш загальне з точки зору оброблюваних типів), навіть якщо воно вимагає деяких розширень GCC (я не знаю, наскільки вони широко підтримуються).


2

Ви не можете отримати доступ до прапора переповнення з C / C ++.

Я не згоден з цим. Ви можете написати якусь вбудовану мову складання та використовувати ajo інструкцію (стрибок переповнення), припускаючи, що ви перебуваєте на x86, щоб захопити переповнення. Звичайно, ваш код більше не переноситься для інших архітектур.

Подивіться на info asі info gcc.


8
вбудований асемблер не має функції C / C ++ і не залежить від платформи. На x86 ви можете використовувати інструкцію istead гілок btw.
Нільс Піпенбрінк

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, додавання безпечне - все ж ви прямуєте безпосередньо до переповнення.
свиня

0

mozilla::CheckedInt<T>надає перевірену переповнення цілочисельну математику для цілого типу T(використовуючи внутрішні символи компілятора на clang та gcc як доступні). Код знаходиться під MPL 2.0 і залежить від трьох ( IntegerTypeTraits.h, Attributes.hі Compiler.h) інших заголовків тільки нестандартним бібліотека заголовки плюс 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

-1

Набір інструкцій x86 включає неподписану команду множення, яка зберігає результат у два регістри. Для використання цієї інструкції з 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
Ви повинні використовувати неподписаний 128-бітний тип, __uint128щоб уникнути переливання підпису та зміщення правого негативного значення.
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

-3

Це залежить, для чого ви його використовуєте. Виконуючи додавання або множення без підпису на довге (DWORD), найкращим рішенням є використання ULARGE_INTEGER.

ULARGE_INTEGER - це структура з двох 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.
Містичний

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

Він працює як для непідписаних значень, так і для позитивних і негативних підписаних значень.

Звичайно, якби ви хотіли зробити щось подібне для зменшення значень замість збільшення значень, ви перевернете <=знак, щоб це зробити >=, припускаючи, що поведінка підтоплення така ж, як і поведінка переповнення. Чесно кажучи, це приблизно так само портативно, оскільки ви отримаєте без доступу до переповненого прапорця процесора (і для цього потрібен вбудований код складання, що робить ваш код у будь-якому разі не портативним у всіх реалізаціях).


9
Якщо підписане значення переповнюється, поведінка вашої програми не визначена. Це не гарантовано загортається.
Девід Стоун
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.