Опишите функцию умножения двух целых обработайте ошибку переполнения сверху overflow

Обработка исключительных ситуаций (overflow) C# Решение и ответ на вопрос 1163295

0 / 0 / 0

Регистрация: 20.03.2013

Сообщений: 101

1

01.05.2014, 13:33. Показов 2265. Ответов 3


помогите.
Опишите функцию умножения двух целых, обработайте ошибку переполнения сверху (overflow).

__________________
Помощь в написании контрольных, курсовых и дипломных работ, диссертаций здесь



0



tranquil

53 / 53 / 10

Регистрация: 26.09.2013

Сообщений: 277

01.05.2014, 13:39

2

C#
1
2
3
4
5
6
7
8
9
10
11
12
int value = 780000000;
checked {
try {
   // Square the original value. 
   int square = value * value; 
   Console.WriteLine("{0} ^ 2 = {1}", value, square);
}
catch (OverflowException) {
   double square = Math.Pow(value, 2);
   Console.WriteLine("Exception: {0} > {1:E}.", 
                     square, Int32.MaxValue);
} }



1



insite2012

01.05.2014, 13:50

 Комментарий модератора 
TF, не дублируйте темы!



0



53 / 53 / 10

Регистрация: 26.09.2013

Сообщений: 277

01.05.2014, 14:08

4

insite2012, он продублировал заголовок темы,задания были разные.

Добавлено через 13 минут
TF, создайте еще раз, назовите темы по разному,но что бы соответствовало заданию)



1



Лабораторная
работа №1

Тема:
Исключения
в С++

Задание:
Реализуйте класс для хранения целых
чисел без знака. Реализуйте метод
умножения двух целых. Породите и
обработайте ошибку переполнения сверху
(overflow).

Выполнил
:Васильковский
В.Н.

//—————————————————————————

#include
<vector.h>

#include
<conio.h>

#include
<iostream.h>

#include
<vcl.h>

#include
<algorith.h>

#pragma
hdrstop

//—————————————————————————

class
Overflow

{

private:

int
num;

public:

Overflow()
{}

Overflow(int
n): num(n){ }

~Overflow()

{

}

Overflow
Overflow::operator *(const Overflow &a1)

{
unsigned int length=400;

if((num
* a1.num)>length) throw («perepolnenie sverhy»);

return
Overflow(num * a1.num);

}

/*void
Umnoj (int a1, int a2)

{
unsigned int length=32768;

if((a1*a2)>length)
throw («perepolnenie sverxy»);

cout<<a1*a2<<endl;

}
*/

};

void
Print(int value)

{

cout<<value<<endl;

}

#pragma
argsused

int
main(int argc, char* argv[])

{
int a1,a2;

Overflow
a,b,c;

try
{

cout<<«vvedite
a i b»;

cin>>a1>>a2;

a=a1;

b=a2;

c=a*b;
}

catch
(const char *error){cout<<error<<endl;}

getch();

return
0;

}

//—————————————————————————

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #


Форум программистов Vingrad

Модераторы: Daevaorn

Поиск:

Ответ в темуСоздание новой темы
Создание опроса
> overflow и underflow 

:(

   

Опции темы

DJVasek
Дата 11.12.2009, 03:53 (ссылка)
| (нет голосов)
Загрузка ... Загрузка …




Быстрая цитата

Цитата

Новичок

Профиль
Группа: Участник
Сообщений: 9
Регистрация: 27.10.2008

Репутация: нет
Всего: нет

Прогаю давно, но вот застрял.
Задание: 
Опишите функцию умножения двух целых, обработайте ошибку перепол-нения сверху (overflow).

Я сделал вот что:

#include <iostream>
using namespace std;

int multiply (int a, int b)
{
    int x = a*b;
    if (a>0 && b>0 || a<0 && b<0)
    {
        if(x<=0)
            throw «error»;                                        
        else
            return x;
    }
    if (a>0 && b<0 || a<0 && b>0)
    {
        if(x>=0)
            throw «error»;                                        
        else
            return x;
    }
}

void main()
{
    // Исключение
    int a = 45, b = a;
    try
    {
        for(;;)
        {
            a = multiply(a, a);
            cout<<b<<» * «<<b<<» = «<<a<<endl;
            b = a;
        }
    }

    catch (char*)
    {
        cout<<b<<» * «<<b<<» = «<<a*a<<» — Overflow!»;
    }

    cout<<endl;
    system(«pause»);
}

вопросы:
1) Я правильно понял задание? Если нет, то объясни что тут имелось ввиду.
2) Если да, то тогда как быть в таком случае:
Опишите функцию деления двух целых, обработайте ошибку переполне-ния снизу (underflow).

Спасибо.

PM MAIL   Вверх
Леопольд
Дата 11.12.2009, 17:05 (ссылка)
| (нет голосов)
Загрузка ... Загрузка …




Быстрая цитата

Цитата

Опытный
**

Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13

Цитата(DJVasek @  11.12.2009,  03:53 Найти цитируемый пост)
Опишите функцию умножения двух целых, обработайте ошибку перепол-нения сверху (overflow).

Для простоты предположим что у нас 8-битный процессор. В восемь битов «влезает» 256 значений, включая ноль.
200 * 3 = 400 % 256 = 88
600 не поместится в 8 битов, однако даже для целого со знаком и результат и множители будут больше ноля. Твои проверки это пропустят.

Как то надо иначе работать.
Возьмём 200 * 3 в двоичной системе: 
11001000 * 11 = 
(2^7 + 2^6 + 2^3) * (2^1 + 2^0) = 
2^(7 + 1 + 0) + 2^(6 + 1 + 0) + 2^(3 + 1 + 0) =
2^8 + 2^7 + 2^4 =             (По первому же слагаемому видно что результат выходит за границы)
110010000

Как реализовать алгоритм. «Смотрим» какие биты выставлены в единицу у меньшего из множителей и складываем их индексы, начиная с ноля. Полученную сумму запоминаем, допустим в var. Берём второй множитель и смотрим какие биты выставлены у него, походу сравниваем сумму текущего индекса и var с пороговым значением. Для 32 битных целых, не должно выходить за 31.

Код

#include <iostream>
#include <algorithm>

template<typename I>
bool checkMultyplyOverflow(I lArg, I rArg){
    enum {
        bitsPerByte = 8,
        correction = static_cast<I>(-1) > 0 ? 0 : 1, //для знаковых на один бит меньще, потому что он нужен для знака
        bound = sizeof(I) * bitsPerByte - correction
    };

    if(lArg > rArg)
        std::swap(lArg, rArg);

    std::size_t summ = 0;
    I one = 1;
    for (std::size_t i = 0; i < bound; ++i)
        if(lArg & (one << i))
            summ += i;

    for (std::size_t i = 0; i < bound; ++i)
        if(rArg & (one << i))
            if(i + summ >= bound)
                return true;
    return false;
}

int main(){
    unsigned char ulArg = 127;
    unsigned char urArg = 2;
    std::cout<<std::boolalpha<<checkMultyplyOverflow(ulArg, urArg)<<std::endl;

    char slArg = 127;
    char srArg = 2;
    std::cout<<std::boolalpha<<checkMultyplyOverflow(slArg, srArg)<<std::endl;

    return 0;
}

Деление посложнее будет, как мне кажется smile

———————

вопросов больше чем ответов

PM MAIL   Вверх
17dufa
Дата 11.12.2009, 17:23 (ссылка)
| (нет голосов)
Загрузка ... Загрузка …




Быстрая цитата

Цитата

Опытный
**

Профиль
Группа: Участник
Сообщений: 324
Регистрация: 2.3.2006

Репутация: 3
Всего: 5

Леопольд, чего-то не понял насчет сложения в меньшем множителе.
пример для 8 битовых беззнаковых:
умножаю 6 * 32 = 192. влезает.
в битах 110 * 100000 
для 110 => sum = 3
для 100000 на i == 5 во втором цикле получаю: if(rArg & (one << i)) истина, i + sum = 8, т.е. if(i + summ >= bound) истина. и функция скажет, что мол переполнение.

подсмотрел тупое, но вроде работающее решение:

Код

bool MulAndTest(__int64 op1, __int64 op2, __int64 &result)
{
        result = op1 * op2;
        if( op2 ) return ( op1 == result/op2 );
        return true;
}

Это сообщение отредактировал(а) 17dufa — 11.12.2009, 17:31

PM MAIL   Вверх
Леопольд
Дата 11.12.2009, 18:05 (ссылка)
| (нет голосов)
Загрузка ... Загрузка …




Быстрая цитата

Цитата

Опытный
**

Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13

Цитата(17dufa @  11.12.2009,  17:23 Найти цитируемый пост)
и функция скажет, что мол переполнение

Значит я что-то не продумал. Но, как мне кажется, надо работать со степенями двойки, возможно как-то иначе…

110 * 100000 = (2^2 + 2^1) * (2^5 + 2^1) = 2^(2+5) + 2^(2+1) + 2^(1+5) + 2^(1+1)

Цитата(17dufa @  11.12.2009,  17:23 Найти цитируемый пост)
Леопольд, чего-то не понял насчет сложения в меньшем множителе.

Ты прав, с суммой я перемудрил. Видимо, надо сравнивать сумму старших разрядов. Так вроде работает.

Код

#include <iostream>

template<typename I>
bool checkMultyplyOverflow(I lArg, I rArg){
    enum {
        bitsPerByte = 8,
        correction = static_cast<I>(-1) > 0 ? 0 : 1, //для знаковых на один бит меньще, потому что он нужен для знака
        bound = sizeof(I) * bitsPerByte - correction
    };

    std::size_t msb = 0; //(most significant bit) старший бит
    I one = 1;
    for (int i = bound - 1; i >= 0; --i)
        if(lArg & (one << i)){
            msb = i;
            break;
        }

    for (int i = bound - 1; i >= 0; --i)
        if(rArg & (one << i)){
            if(i + msb >= bound)
                return true;
            break;
        }
    return false;
}

int main(){
    unsigned char ulArg = 2;
    unsigned char urArg = 127;
    std::cout<<std::boolalpha<<checkMultyplyOverflow(ulArg, urArg)<<std::endl;

    ulArg = 32;
    urArg = 6;
    std::cout<<std::boolalpha<<checkMultyplyOverflow(ulArg, urArg)<<std::endl;

    char slArg = 127;
    char srArg = 2;
    std::cout<<std::boolalpha<<checkMultyplyOverflow(slArg, srArg)<<std::endl;

    return 0;
}

Это сообщение отредактировал(а) Леопольд — 11.12.2009, 18:27

———————

вопросов больше чем ответов

PM MAIL   Вверх
Леопольд
Дата 11.12.2009, 18:37 (ссылка)
| (нет голосов)
Загрузка ... Загрузка …




Быстрая цитата

Цитата

Опытный
**

Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13

Цитата(17dufa @  11.12.2009,  17:23 Найти цитируемый пост)
подсмотрел тупое, но вроде работающее решение:

Код

bool MulAndTest(__int64 op1, __int64 op2, __int64 &result)
{
        result = op1 * op2;
        if( op2 ) return ( op1 == result/op2 );
        return true;
}

Вполне логичное решение.
Надо только профайлером посмотреть что быстрее…

Это сообщение отредактировал(а) Леопольд — 11.12.2009, 18:47

———————

вопросов больше чем ответов

PM MAIL   Вверх
DJVasek
Дата 11.12.2009, 18:48 (ссылка)
| (нет голосов)
Загрузка ... Загрузка …




Быстрая цитата

Цитата

Новичок

Профиль
Группа: Участник
Сообщений: 9
Регистрация: 27.10.2008

Репутация: нет
Всего: нет

Спасиб мужыки))) Теперь понял))))

А тогда как быть с этим:
Опишите функцию деления двух целых, обработайте ошибку переполне-ния снизу (underflow)?

Это сообщение отредактировал(а) DJVasek — 11.12.2009, 22:10

PM MAIL   Вверх



















Ответ в темуСоздание новой темы
Создание опроса
Правила форума «С++:Общие вопросы»
Earnest
Daevaorn

Добро пожаловать!

  • Черновик стандарта C++ (за октябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл
    черновика(4.4мб).
  • Черновик стандарта C (за сентябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика (3.4мб).
  • Прежде чем задать вопрос, прочтите это и/или это!
  • Здесь хранится весь мировой запас ссылок на документы, связанные с C++ :)
  • Не брезгуйте пользоваться тегами [code=cpp][/code].
  • Пожалуйста, не просите написать за вас программы в этом разделе — для этого существует «Центр Помощи».
  • C++ FAQ

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением,
Earnest
Daevaorn

 

0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема »

1. Detecting the overflow:

x = a * b;
if (a != 0 && x / a != b) {
    // overflow handling
}

Edit: Fixed division by 0 (thanks Mark!)

2. Computing the carry is quite involved. One approach is to split both operands into half-words, then apply long multiplication to the half-words:

uint64_t hi(uint64_t x) {
    return x >> 32;
}

uint64_t lo(uint64_t x) {
    return ((1ULL << 32) - 1) & x;
}

void multiply(uint64_t a, uint64_t b) {
    // actually uint32_t would do, but the casting is annoying
    uint64_t s0, s1, s2, s3; 
    
    uint64_t x = lo(a) * lo(b);
    s0 = lo(x);
    
    x = hi(a) * lo(b) + hi(x);
    s1 = lo(x);
    s2 = hi(x);
    
    x = s1 + lo(a) * hi(b);
    s1 = lo(x);
    
    x = s2 + hi(a) * hi(b) + hi(x);
    s2 = lo(x);
    s3 = hi(x);
    
    uint64_t result = s1 << 32 | s0;
    uint64_t carry = s3 << 32 | s2;
}

To see that none of the partial sums themselves can overflow, we consider the worst case:

        x = s2 + hi(a) * hi(b) + hi(x)

Let B = 1 << 32. We then have

            x <= (B - 1) + (B - 1)(B - 1) + (B - 1)
              <= B*B - 1
               < B*B

I believe this will work — at least it handles Sjlver’s test case. Aside from that, it is untested (and might not even compile, as I don’t have a C++ compiler at hand anymore).

answered Nov 29, 2009 at 12:17

meriton's user avatar

meritonmeriton

67.3k14 gold badges107 silver badges173 bronze badges

15

The idea is to use following fact which is true for integral operation:

a*b > c if and only if a > c/b

/ is integral division here.

The pseudocode to check against overflow for positive numbers follows:

if (a > max_int64 / b) then «overflow» else «ok».

To handle zeroes and negative numbers you should add more checks.

C code for non-negative a and b follows:

if (b > 0 && a > 18446744073709551615 / b) {
     // overflow handling
}; else {
    c = a * b;
}

Note, max value for 64 type:

18446744073709551615 == (1<<64)-1

To calculate carry we can use approach to split number into two 32-digits and multiply them as we do this on the paper. We need to split numbers to avoid overflow.

Code follows:

// split input numbers into 32-bit digits
uint64_t a0 = a & ((1LL<<32)-1);
uint64_t a1 = a >> 32;
uint64_t b0 = b & ((1LL<<32)-1);
uint64_t b1 = b >> 32;


// The following 3 lines of code is to calculate the carry of d1
// (d1 - 32-bit second digit of result, and it can be calculated as d1=d11+d12),
// but to avoid overflow.
// Actually rewriting the following 2 lines:
// uint64_t d1 = (a0 * b0 >> 32) + a1 * b0 + a0 * b1;
// uint64_t c1 = d1 >> 32;
uint64_t d11 = a1 * b0 + (a0 * b0 >> 32); 
uint64_t d12 = a0 * b1;
uint64_t c1 = (d11 > 18446744073709551615 - d12) ? 1 : 0;

uint64_t d2 = a1 * b1 + c1;
uint64_t carry = d2; // needed carry stored here

answered Nov 29, 2009 at 12:29

sergtk's user avatar

sergtksergtk

10.5k15 gold badges75 silver badges129 bronze badges

9

Although there have been several other answers to this question, I several of them have code that is completely untested, and thus far no one has adequately compared the different possible options.

For that reason, I wrote and tested several possible implementations (the last one is based on this code from OpenBSD, discussed on Reddit here). Here’s the code:

/* Multiply with overflow checking, emulating clang's builtin function
 *
 *     __builtin_umull_overflow
 *
 * This code benchmarks five possible schemes for doing so.
 */

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <limits.h>

#ifndef BOOL
    #define BOOL int
#endif

// Option 1, check for overflow a wider type
//    - Often fastest and the least code, especially on modern compilers
//    - When long is a 64-bit int, requires compiler support for 128-bits
//      ints (requires GCC >= 3.0 or Clang)

#if LONG_BIT > 32
    typedef __uint128_t long_overflow_t ;
#else
    typedef uint64_t long_overflow_t;
#endif

BOOL 
umull_overflow1(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        long_overflow_t prod = (long_overflow_t)lhs * (long_overflow_t)rhs;
        *result = (unsigned long) prod;
        return (prod >> LONG_BIT) != 0;
}

// Option 2, perform long multiplication using a smaller type
//    - Sometimes the fastest (e.g., when mulitply on longs is a library
//      call).
//    - Performs at most three multiplies, and sometimes only performs one.
//    - Highly portable code; works no matter how many bits unsigned long is

BOOL 
umull_overflow2(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        const unsigned long HALFSIZE_MAX = (1ul << LONG_BIT/2) - 1ul;
        unsigned long lhs_high = lhs >> LONG_BIT/2;
        unsigned long lhs_low  = lhs & HALFSIZE_MAX;
        unsigned long rhs_high = rhs >> LONG_BIT/2;
        unsigned long rhs_low  = rhs & HALFSIZE_MAX;

        unsigned long bot_bits = lhs_low * rhs_low;
        if (!(lhs_high || rhs_high)) {
            *result = bot_bits;
            return 0; 
        }
        BOOL overflowed = lhs_high && rhs_high;
        unsigned long mid_bits1 = lhs_low * rhs_high;
        unsigned long mid_bits2 = lhs_high * rhs_low;

        *result = bot_bits + ((mid_bits1+mid_bits2) << LONG_BIT/2);
        return overflowed || *result < bot_bits
            || (mid_bits1 >> LONG_BIT/2) != 0
            || (mid_bits2 >> LONG_BIT/2) != 0;
}

// Option 3, perform long multiplication using a smaller type (this code is
// very similar to option 2, but calculates overflow using a different but
// equivalent method).
//    - Sometimes the fastest (e.g., when mulitply on longs is a library
//      call; clang likes this code).
//    - Performs at most three multiplies, and sometimes only performs one.
//    - Highly portable code; works no matter how many bits unsigned long is

BOOL 
umull_overflow3(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        const unsigned long HALFSIZE_MAX = (1ul << LONG_BIT/2) - 1ul;
        unsigned long lhs_high = lhs >> LONG_BIT/2;
        unsigned long lhs_low  = lhs & HALFSIZE_MAX;
        unsigned long rhs_high = rhs >> LONG_BIT/2;
        unsigned long rhs_low  = rhs & HALFSIZE_MAX;

        unsigned long lowbits = lhs_low * rhs_low;
        if (!(lhs_high || rhs_high)) {
            *result = lowbits;
            return 0; 
        }
        BOOL overflowed = lhs_high && rhs_high;
        unsigned long midbits1 = lhs_low * rhs_high;
        unsigned long midbits2 = lhs_high * rhs_low;
        unsigned long midbits  = midbits1 + midbits2;
        overflowed = overflowed || midbits < midbits1 || midbits > HALFSIZE_MAX;
        unsigned long product = lowbits + (midbits << LONG_BIT/2);
        overflowed = overflowed || product < lowbits;

        *result = product;
        return overflowed;
}

// Option 4, checks for overflow using division
//    - Checks for overflow using division
//    - Division is slow, especially if it is a library call

BOOL
umull_overflow4(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        *result = lhs * rhs;
        return rhs > 0 && (SIZE_MAX / rhs) < lhs;
}

// Option 5, checks for overflow using division
//    - Checks for overflow using division
//    - Avoids division when the numbers are "small enough" to trivially
//      rule out overflow
//    - Division is slow, especially if it is a library call

BOOL
umull_overflow5(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        const unsigned long MUL_NO_OVERFLOW = (1ul << LONG_BIT/2) - 1ul;
        *result = lhs * rhs;
        return (lhs >= MUL_NO_OVERFLOW || rhs >= MUL_NO_OVERFLOW) &&
            rhs > 0 && SIZE_MAX / rhs < lhs;
}

#ifndef umull_overflow
    #define umull_overflow2
#endif

/*
 * This benchmark code performs a multiply at all bit sizes, 
 * essentially assuming that sizes are logarithmically distributed.
 */

int main()
{
        unsigned long i, j, k;
        int count = 0;
        unsigned long mult;
        unsigned long total = 0;

        for (k = 0; k < 0x40000000 / LONG_BIT / LONG_BIT; ++k)
                for (i = 0; i != LONG_MAX; i = i*2+1)
                        for (j = 0; j != LONG_MAX; j = j*2+1) {
                                count += umull_overflow(i+k, j+k, &mult);
                                total += mult;
                        }
        printf("%d overflows (total %lu)n", count, total);
}

Here are the results, testing with various compilers and systems I have (in this case, all testing was done on OS X, but results should be similar on BSD or Linux systems):

+------------------+----------+----------+----------+----------+----------+
|                  | Option 1 | Option 2 | Option 3 | Option 4 | Option 5 |
|                  |  BigInt  | LngMult1 | LngMult2 |   Div    |  OptDiv  |
+------------------+----------+----------+----------+----------+----------+
| Clang 3.5 i386   |    1.610 |    3.217 |    3.129 |    4.405 |    4.398 |
| GCC 4.9.0 i386   |    1.488 |    3.469 |    5.853 |    4.704 |    4.712 |
| GCC 4.2.1 i386   |    2.842 |    4.022 |    3.629 |    4.160 |    4.696 |
| GCC 4.2.1 PPC32  |    8.227 |    7.756 |    7.242 |   20.632 |   20.481 |
| GCC 3.3   PPC32  |    5.684 |    9.804 |   11.525 |   21.734 |   22.517 |
+------------------+----------+----------+----------+----------+----------+
| Clang 3.5 x86_64 |    1.584 |    2.472 |    2.449 |    9.246 |    7.280 |
| GCC 4.9 x86_64   |    1.414 |    2.623 |    4.327 |    9.047 |    7.538 |
| GCC 4.2.1 x86_64 |    2.143 |    2.618 |    2.750 |    9.510 |    7.389 |
| GCC 4.2.1 PPC64  |   13.178 |    8.994 |    8.567 |   37.504 |   29.851 |
+------------------+----------+----------+----------+----------+----------+

Based on these results, we can draw a few conclusions:

  • Clearly, the division-based approach, although simple and portable, is slow.
  • No technique is a clear winner in all cases.
  • On modern compilers, the use-a-larger-int approach is best, if you can use it
  • On older compilers, the long-multiplication approach is best
  • Surprisingly, GCC 4.9.0 has performance regressions over GCC 4.2.1, and GCC 4.2.1 has performance regressions over GCC 3.3

answered Oct 12, 2014 at 0:33

Charphacy's user avatar

CharphacyCharphacy

2,0201 gold badge20 silver badges12 bronze badges

A version that also works when a == 0:

    x = a * b;
    if (a != 0 && x / a != b) {
        // overflow handling
    }

answered Nov 29, 2009 at 12:28

Mark Byers's user avatar

Mark ByersMark Byers

795k188 gold badges1562 silver badges1443 bronze badges

4

Easy and fast with clang and gcc:

unsigned long long t a, b, result;
if (__builtin_umulll_overflow(a, b, &result)) {
    // overflow!!
}

This will use hardware support for overflow detection where available. By being compiler extensions it can even handle signed integer overflow (replace umul with smul), eventhough that is undefined behavior in C++.

answered Jul 29, 2019 at 20:12

Allan Jensen's user avatar

0

If you need not just to detect overflow but also to capture the carry, you’re best off breaking your numbers down into 32-bit parts. The code is a nightmare; what follows is just a sketch:

#include <stdint.h>

uint64_t mul(uint64_t a, uint64_t b) {
  uint32_t ah = a >> 32;
  uint32_t al = a;  // truncates: now a = al + 2**32 * ah
  uint32_t bh = b >> 32;
  uint32_t bl = b;  // truncates: now b = bl + 2**32 * bh
  // a * b = 2**64 * ah * bh + 2**32 * (ah * bl + bh * al) + al * bl
  uint64_t partial = (uint64_t) al * (uint64_t) bl;
  uint64_t mid1    = (uint64_t) ah * (uint64_t) bl;
  uint64_t mid2    = (uint64_t) al * (uint64_t) bh;
  uint64_t carry   = (uint64_t) ah * (uint64_t) bh;
  // add high parts of mid1 and mid2 to carry
  // add low parts of mid1 and mid2 to partial, carrying
  //    any carry bits into carry...
}

The problem is not just the partial products but the fact that any of the sums can overflow.

If I had to do this for real, I would write an extended-multiply routine in the local assembly language. That is, for example, multiply two 64-bit integers to get a 128-bit result, which is stored in two 64-bit registers. All reasonable hardware provides this functionality in a single native multiply instruction—it’s not just accessible from C.

This is one of those rare cases where the solution that’s most elegant and easy to program is actually to use assembly language. But it’s certainly not portable :-(

answered Nov 30, 2009 at 1:29

Norman Ramsey's user avatar

Norman RamseyNorman Ramsey

197k59 gold badges359 silver badges531 bronze badges

2

The GNU Portability Library (Gnulib) contains a module intprops, which has macros that efficiently test whether arithmetic operations would overflow.

For example, if an overflow in multiplication would occur, INT_MULTIPLY_OVERFLOW (a, b) would yield 1.

answered Oct 11, 2019 at 20:58

Marc's user avatar

MarcMarc

4,3074 gold badges30 silver badges46 bronze badges

Perhaps the best way to solve this problem is to have a function, which multiplies two UInt64 and results a pair of UInt64, an upper part and a lower part of the UInt128 result. Here is the solution, including a function, which displays the result in hex. I guess you perhaps prefer a C++ solution, but I have a working Swift-Solution which shows, how to manage the problem:

func hex128 (_ hi: UInt64, _ lo: UInt64) -> String
{
    var s: String = String(format: "%08X", hi >> 32)
                  + String(format: "%08X", hi & 0xFFFFFFFF)
                  + String(format: "%08X", lo >> 32)
                  + String(format: "%08X", lo & 0xFFFFFFFF)
    return (s)
}

func mul64to128 (_ multiplier: UInt64, _ multiplicand : UInt64)
             -> (result_hi: UInt64, result_lo: UInt64)
{
    let x: UInt64 = multiplier
    let x_lo: UInt64 = (x & 0xffffffff)
    let x_hi: UInt64 = x >> 32

    let y: UInt64 = multiplicand
    let y_lo: UInt64 = (y & 0xffffffff)
    let y_hi: UInt64 = y >> 32

    let mul_lo: UInt64 = (x_lo * y_lo)
    let mul_hi: UInt64 = (x_hi * y_lo) + (mul_lo >> 32)
    let mul_carry: UInt64 = (x_lo * y_hi) + (mul_hi & 0xffffffff)
    let result_hi: UInt64 = (x_hi * y_hi) + (mul_hi >> 32) + (mul_carry >> 32)
    let result_lo: UInt64 = (mul_carry << 32) + (mul_lo & 0xffffffff)

    return (result_hi, result_lo)
}

Here is an example to verify, that the function works:

var c: UInt64 = 0
var d: UInt64 = 0

(c, d) = mul64to128(0x1234567890123456, 0x9876543210987654)
// 0AD77D742CE3C72E45FD10D81D28D038 is the result of the above example
print(hex128(c, d))

(c, d) = mul64to128(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF)
// FFFFFFFFFFFFFFFE0000000000000001 is the result of the above example
print(hex128(c, d))

answered Feb 23, 2017 at 21:55

j.s.com's user avatar

j.s.comj.s.com

1,38212 silver badges23 bronze badges

0

There is a simple (and often very fast solution) which has not been mentioned yet. The solution is based on the fact that n-Bit times m-Bit multiplication does never overflow for a product width of n+m-bit or higher but overflows for all result widths smaller than n+m-1.

Because my old description might have been too difficult to read for some people, I try it again:
What you need is checking the sum of leading-zeroes of both operands. It would be very easy to prove mathematically.
Let x be n-Bit and y be m-Bit. z = x * y is k-Bit. Because the product can be n+m bit large at most it can overflow. Let’s say. x*y is p-Bit long (without leading zeroes). The leading zeroes of the product are clz(x * y) = n+m - p. clz behaves similar to log, hence:
clz(x * y) = clz(x) + clz(y) + c with c = either 1 or 0.
(thank you for the c = 1 advice in the comment!)
It overflows when k < p <= n+m <=> n+m - k > n+m - p = clz(x * y).

Now we can use this algorithm:

if max(clz(x * y)) = clz(x) + clz(y) +1 < (n+m - k)  --> overflow
if max(clz(x * y)) = clz(x) + clz(y) +1 == (n+m - k)  --> overflow if c = 0
else --> no overflow

How to check for overflow in the middle case? I assume, you have a multiplication instruction. Then we easily can use it to see the leading zeroes of the result, i.e.:

if clz(x * y / 2) == (n+m - k) <=> msb(x * y/2) == 1  --> overflow
else --> no overflow

You do the multiplication by treating x/2 as fixed point and y as normal integer:

msb(x * y/2) = msb(floor(x * y / 2))
floor(x * y/2) = floor(x/2) * y + (lsb(x) * floor(y/2)) = (x >> 1)*y + (x & 1)*(y >> 1)

(this result never overflows in case of clz(x)+clz(y)+1 == (n+m -k))

The trick is using builtins/intrinsics. In GCC it looks this way:

static inline int clz(int a) {
    if (a == 0) return 32; //only needed for x86 architecture
    return __builtin_clz(a);
}
/**@fn static inline _Bool chk_mul_ov(uint32_t f1, uint32_t f2)
 * @return one, if a 32-Bit-overflow occurs when unsigned-unsigned-multipliying f1 with f2 otherwise zero. */
static inline _Bool chk_mul_ov(uint32_t f1, uint32_t f2) {
    int lzsum = clz(f1) + clz(f2); //leading zero sum
    return
        lzsum < sizeof(f1)*8-1 || ( //if too small, overflow guaranteed
            lzsum == sizeof(f1)*8-1 && //if special case, do further check
            (int32_t)((f1 >> 1)*f2 + (f1 & 1)*(f2 >> 1)) < 0 //check product rightshifted by one
    );
}
...
    if (chk_mul_ov(f1, f2)) {
        //error handling
    }
...

Just an example for n = m = k = 32-Bit unsigned-unsigned-multiplication. You can generalize it to signed-unsigned- or signed-signed-multiplication. And even no multiple-bit-shift is required (because some microcontrollers implement one-bit-shifts only but sometimes support product divided by two with a single instruction like Atmega!). However, if no count-leading-zeroes instruction exists but long multiplication, this might not be better.

Other compilers have their own way of specifying intrinsics for CLZ operations.
Compared to checking upper half of the multiplication the clz-method should scale better (in worst case) than using a highly optimized 128-Bit multiplication to check for 64-Bit overflow. Multiplication needs over linear overhead while count bits needs only linear overhead.
This code worked out-of-the box for me when tried.

answered Nov 29, 2019 at 18:20

ChrisoLosoph's user avatar

2

I’ve been working with this problem this days and I have to say that it has impressed me the number of times I have seen people saying the best way to know if there has been an overflow is to divide the result, thats totally inefficient and unnecessary. The point for this function is that it must be as fast as possible.

There are two options for the overflow detection:

1º- If possible create the result variable twice as big as the multipliers, for example:

struct INT32struct {INT16 high, low;};
typedef union
{
  struct INT32struct s;
  INT32 ll;
} INT32union;

INT16 mulFunction(INT16 a, INT16 b)
{
  INT32union result.ll = a * b; //32Bits result
  if(result.s.high > 0) 
      Overflow();
  return (result.s.low)
}

You will know inmediately if there has been an overflow, and the code is the fastest possible without writing it in machine code. Depending on the compiler this code can be improved in machine code.

2º- Is impossible to create a result variable twice as big as the multipliers variable:
Then you should play with if conditions to determine the best path. Continuing with the example:

INT32 mulFunction(INT32 a, INT32 b)
{

  INT32union s_a.ll = abs(a);
  INT32union s_b.ll = abs(b); //32Bits result
  INT32union result;
  if(s_a.s.hi > 0 && s_b.s.hi > 0)
  {
      Overflow();
  }
  else if (s_a.s.hi > 0)
  {
      INT32union res1.ll = s_a.s.hi * s_b.s.lo;
      INT32union res2.ll = s_a.s.lo * s_b.s.lo;
      if (res1.hi == 0)
      {
          result.s.lo = res1.s.lo + res2.s.hi;
          if (result.s.hi == 0)
          {
            result.s.ll = result.s.lo << 16 + res2.s.lo;
            if ((a.s.hi >> 15) ^ (b.s.hi >> 15) == 1)
            {
                result.s.ll = -result.s.ll; 
            }
            return result.s.ll
          }else
          {
             Overflow();
          }
      }else
      {
          Overflow();
      }
  }else if (s_b.s.hi > 0)
{

   //Same code changing a with b

}else 
{
    return (s_a.lo * s_b.lo);
}
}

I hope this code helps you to have a quite efficient program and I hope the code is clear, if not I’ll put some coments.

best regards.

answered Mar 11, 2013 at 19:34

user1368116's user avatar

3

Here is a trick for detecting whether multiplication of two unsigned integers overflows.

We make the observation that if we multiply an N-bit-wide binary number with an M-bit-wide binary number, the product does not have more than N + M bits.

For instance, if we are asked to multiply a three-bit number with a twenty-nine bit number, we know that this doesn’t overflow thirty-two bits.

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

int might_be_mul_oflow(unsigned long a, unsigned long b)
{
  if (!a || !b)
    return 0;

  a = a | (a >> 1) | (a >> 2) | (a >> 4) | (a >> 8) | (a >> 16) | (a >> 32);
  b = b | (b >> 1) | (b >> 2) | (b >> 4) | (b >> 8) | (b >> 16) | (b >> 32);

  for (;;) {
    unsigned long na = a << 1;
    if (na <= a)
      break;
    a = na;
  }

  return (a & b) ? 1 : 0;
}

int main(int argc, char **argv)
{
  unsigned long a, b;
  char *endptr;

  if (argc < 3) {
    printf("supply two unsigned long integers in C formn");
    return EXIT_FAILURE;
  }

  a = strtoul(argv[1], &endptr, 0);

  if (*endptr != 0) {
    printf("%s is garbagen", argv[1]);
    return EXIT_FAILURE;
  }

  b = strtoul(argv[2], &endptr, 0);

  if (*endptr != 0) {
    printf("%s is garbagen", argv[2]);
    return EXIT_FAILURE;
  }

  if (might_be_mul_oflow(a, b))
    printf("might be multiplication overflown");

  {
    unsigned long c = a * b;
    printf("%lu * %lu = %lun", a, b, c);
    if (a != 0 && c / a != b)
      printf("confirmed multiplication overflown");
  }

  return 0;
}

A smattering of tests: (on 64 bit system):

$ ./uflow 0x3 0x3FFFFFFFFFFFFFFF
3 * 4611686018427387903 = 13835058055282163709

$ ./uflow 0x7 0x3FFFFFFFFFFFFFFF
might be multiplication overflow
7 * 4611686018427387903 = 13835058055282163705
confirmed multiplication overflow

$ ./uflow 0x4 0x3FFFFFFFFFFFFFFF
might be multiplication overflow
4 * 4611686018427387903 = 18446744073709551612

$ ./uflow 0x5 0x3FFFFFFFFFFFFFFF
might be multiplication overflow
5 * 4611686018427387903 = 4611686018427387899
confirmed multiplication overflow

The steps in might_be_mul_oflow are almost certainly slower than just doing the division test, at least on mainstream processors used in desktop workstations, servers and mobile devices. On chips without good division support, it could be useful.


It occurs to me that there is another way to do this early rejection test.

  1. We start with a pair of numbers arng and brng which are initialized to 0x7FFF...FFFF and 1.

  2. If a <= arng and b <= brng we can conclude that there is no overflow.

  3. Otherwise, we shift arng to the right, and shift brng to the left, adding one bit to brng, so that they are 0x3FFF...FFFF and 3.

  4. If arng is zero, finish; otherwise repeat at 2.

The function now looks like:

int might_be_mul_oflow(unsigned long a, unsigned long b)
{
  if (!a || !b)
    return 0;

  {
    unsigned long arng = ULONG_MAX >> 1;
    unsigned long brng = 1;

    while (arng != 0) {
      if (a <= arng && b <= brng)
        return 0;
      arng >>= 1;
      brng <<= 1;
      brng |= 1;
    }

    return 1;
  }
}

answered Jun 12, 2017 at 18:42

Kaz's user avatar

KazKaz

54.1k9 gold badges97 silver badges145 bronze badges

When your using e.g. 64 bits variables, implement ‘number of significant bits’ with nsb(var) = { 64 — clz(var); }.

clz(var) = count leading zeros in var, a builtin command for GCC and Clang, or probably available with inline assembly for your CPU.

Now use the fact that nsb(a * b) <= nsb(a) + nsb(b) to check for overflow. When smaller, it is always 1 smaller.

Ref GCC: Built-in Function: int __builtin_clz (unsigned int x)
Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined.

answered Jan 11, 2021 at 7:31

Steven Buytaert's user avatar

I was thinking about this today and stumbled upon this question, my thoughts led me to this result. TLDR, while I find it «elegant» in that it only uses a few lines of code (could easily be a one liner), and has some mild math that simplifies to something relatively simple conceptually, this is mostly «interesting» and I haven’t tested it.

If you think of an unsigned integer as being a single digit with radix 2^n where n is the number of bits in the integer, then you can map those numbers to radians around the unit circle, e.g.

radians(x) = x * (2 * pi * rad / 2^n)

When the integer overflows, it is equivalent to wrapping around the circle. So calculating the carry is equivalent to calculating the number of times multiplication would wrap around the circle. To calculate the number of times we wrap around the circle we divide radians(x) by 2pi radians. e.g.

wrap(x) = radians(x) / (2*pi*rad)
        = (x * (2*pi*rad / 2^n)) / (2*pi*rad / 1)
        = (x * (2*pi*rad / 2^n)) * (1 / 2*pi*rad)
        = x * 1 / 2^n
        = x / 2^n

Which simplifies to

wrap(x) = x / 2^n

This makes sense. The number of times a number, for example, 15 with radix 10, wraps around is 15 / 10 = 1.5, or one and a half times. However, we can’t use 2 digits here (assuming we are limited to a single 2^64 digit).

Say we have a * b, with radix R, we can calculate the carry with

Consider that: wrap(a * b) = a * wrap(b)
wrap(a * b) = (a * b) / R
a * wrap(b) = a * (b / R)
a * (b / R) = (a * b) / R

carry = floor(a * wrap(b))

Take for example a = 9 and b = 5, which are factors of 45 (i.e. 9 * 5 = 45).

wrap(5) = 5 / 10 = 0.5
a * wrap(5) = 9 * 0.5 = 4.5
carry = floor(9 * wrap(5)) = floor(4.5) = 4

Note that if the carry was 0, then we would not have had overflow, for example if a = 2, b=2.

In C/C++ (if the compiler and architecture supports it) we have to use long double.

Thus we have:

long double wrap = b / 18446744073709551616.0L; // this is b / 2^64
unsigned long carry = (unsigned long)(a * wrap); // floor(a * wrap(b))
bool overflow = carry > 0;
unsigned long c = a * b;

c here is the lower significant «digit», i.e. in base 10 9 * 9 = 81, carry = 8, and c = 1.

This was interesting to me in theory, so I thought I’d share it, but one major caveat is with the floating point precision in computers. Using long double, there may be rounding errors for some numbers when we calculate the wrap variable depending on how many significant digits your compiler/arch uses for long doubles, I believe it should be 20 more more to be sure. Another issue with this result, is that it may not perform as well as some of the other solutions simply by using floating points and division.

answered Apr 19, 2021 at 20:14

Todd Fulton's user avatar

If you just want to detect overflow, how about converting to double, doing the multiplication and if

|x| < 2^53, convert to int64

|x| < 2^63, make the multiplication using int64

otherwise produce whatever error you want?

This seems to work:

int64_t safemult(int64_t a, int64_t b) {
  double dx;

  dx = (double)a * (double)b;

  if ( fabs(dx) < (double)9007199254740992 )
    return (int64_t)dx;

  if ( (double)INT64_MAX < fabs(dx) )
    return INT64_MAX;

  return a*b;
}

answered Oct 13, 2017 at 15:28

Gunnar Thorburn's user avatar

1

1. Detecting the overflow:

x = a * b;
if (a != 0 && x / a != b) {
    // overflow handling
}

Edit: Fixed division by 0 (thanks Mark!)

2. Computing the carry is quite involved. One approach is to split both operands into half-words, then apply long multiplication to the half-words:

uint64_t hi(uint64_t x) {
    return x >> 32;
}

uint64_t lo(uint64_t x) {
    return ((1ULL << 32) - 1) & x;
}

void multiply(uint64_t a, uint64_t b) {
    // actually uint32_t would do, but the casting is annoying
    uint64_t s0, s1, s2, s3; 
    
    uint64_t x = lo(a) * lo(b);
    s0 = lo(x);
    
    x = hi(a) * lo(b) + hi(x);
    s1 = lo(x);
    s2 = hi(x);
    
    x = s1 + lo(a) * hi(b);
    s1 = lo(x);
    
    x = s2 + hi(a) * hi(b) + hi(x);
    s2 = lo(x);
    s3 = hi(x);
    
    uint64_t result = s1 << 32 | s0;
    uint64_t carry = s3 << 32 | s2;
}

To see that none of the partial sums themselves can overflow, we consider the worst case:

        x = s2 + hi(a) * hi(b) + hi(x)

Let B = 1 << 32. We then have

            x <= (B - 1) + (B - 1)(B - 1) + (B - 1)
              <= B*B - 1
               < B*B

I believe this will work — at least it handles Sjlver’s test case. Aside from that, it is untested (and might not even compile, as I don’t have a C++ compiler at hand anymore).

answered Nov 29, 2009 at 12:17

meriton's user avatar

meritonmeriton

67.3k14 gold badges107 silver badges173 bronze badges

15

The idea is to use following fact which is true for integral operation:

a*b > c if and only if a > c/b

/ is integral division here.

The pseudocode to check against overflow for positive numbers follows:

if (a > max_int64 / b) then «overflow» else «ok».

To handle zeroes and negative numbers you should add more checks.

C code for non-negative a and b follows:

if (b > 0 && a > 18446744073709551615 / b) {
     // overflow handling
}; else {
    c = a * b;
}

Note, max value for 64 type:

18446744073709551615 == (1<<64)-1

To calculate carry we can use approach to split number into two 32-digits and multiply them as we do this on the paper. We need to split numbers to avoid overflow.

Code follows:

// split input numbers into 32-bit digits
uint64_t a0 = a & ((1LL<<32)-1);
uint64_t a1 = a >> 32;
uint64_t b0 = b & ((1LL<<32)-1);
uint64_t b1 = b >> 32;


// The following 3 lines of code is to calculate the carry of d1
// (d1 - 32-bit second digit of result, and it can be calculated as d1=d11+d12),
// but to avoid overflow.
// Actually rewriting the following 2 lines:
// uint64_t d1 = (a0 * b0 >> 32) + a1 * b0 + a0 * b1;
// uint64_t c1 = d1 >> 32;
uint64_t d11 = a1 * b0 + (a0 * b0 >> 32); 
uint64_t d12 = a0 * b1;
uint64_t c1 = (d11 > 18446744073709551615 - d12) ? 1 : 0;

uint64_t d2 = a1 * b1 + c1;
uint64_t carry = d2; // needed carry stored here

answered Nov 29, 2009 at 12:29

sergtk's user avatar

sergtksergtk

10.5k15 gold badges75 silver badges129 bronze badges

9

Although there have been several other answers to this question, I several of them have code that is completely untested, and thus far no one has adequately compared the different possible options.

For that reason, I wrote and tested several possible implementations (the last one is based on this code from OpenBSD, discussed on Reddit here). Here’s the code:

/* Multiply with overflow checking, emulating clang's builtin function
 *
 *     __builtin_umull_overflow
 *
 * This code benchmarks five possible schemes for doing so.
 */

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <limits.h>

#ifndef BOOL
    #define BOOL int
#endif

// Option 1, check for overflow a wider type
//    - Often fastest and the least code, especially on modern compilers
//    - When long is a 64-bit int, requires compiler support for 128-bits
//      ints (requires GCC >= 3.0 or Clang)

#if LONG_BIT > 32
    typedef __uint128_t long_overflow_t ;
#else
    typedef uint64_t long_overflow_t;
#endif

BOOL 
umull_overflow1(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        long_overflow_t prod = (long_overflow_t)lhs * (long_overflow_t)rhs;
        *result = (unsigned long) prod;
        return (prod >> LONG_BIT) != 0;
}

// Option 2, perform long multiplication using a smaller type
//    - Sometimes the fastest (e.g., when mulitply on longs is a library
//      call).
//    - Performs at most three multiplies, and sometimes only performs one.
//    - Highly portable code; works no matter how many bits unsigned long is

BOOL 
umull_overflow2(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        const unsigned long HALFSIZE_MAX = (1ul << LONG_BIT/2) - 1ul;
        unsigned long lhs_high = lhs >> LONG_BIT/2;
        unsigned long lhs_low  = lhs & HALFSIZE_MAX;
        unsigned long rhs_high = rhs >> LONG_BIT/2;
        unsigned long rhs_low  = rhs & HALFSIZE_MAX;

        unsigned long bot_bits = lhs_low * rhs_low;
        if (!(lhs_high || rhs_high)) {
            *result = bot_bits;
            return 0; 
        }
        BOOL overflowed = lhs_high && rhs_high;
        unsigned long mid_bits1 = lhs_low * rhs_high;
        unsigned long mid_bits2 = lhs_high * rhs_low;

        *result = bot_bits + ((mid_bits1+mid_bits2) << LONG_BIT/2);
        return overflowed || *result < bot_bits
            || (mid_bits1 >> LONG_BIT/2) != 0
            || (mid_bits2 >> LONG_BIT/2) != 0;
}

// Option 3, perform long multiplication using a smaller type (this code is
// very similar to option 2, but calculates overflow using a different but
// equivalent method).
//    - Sometimes the fastest (e.g., when mulitply on longs is a library
//      call; clang likes this code).
//    - Performs at most three multiplies, and sometimes only performs one.
//    - Highly portable code; works no matter how many bits unsigned long is

BOOL 
umull_overflow3(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        const unsigned long HALFSIZE_MAX = (1ul << LONG_BIT/2) - 1ul;
        unsigned long lhs_high = lhs >> LONG_BIT/2;
        unsigned long lhs_low  = lhs & HALFSIZE_MAX;
        unsigned long rhs_high = rhs >> LONG_BIT/2;
        unsigned long rhs_low  = rhs & HALFSIZE_MAX;

        unsigned long lowbits = lhs_low * rhs_low;
        if (!(lhs_high || rhs_high)) {
            *result = lowbits;
            return 0; 
        }
        BOOL overflowed = lhs_high && rhs_high;
        unsigned long midbits1 = lhs_low * rhs_high;
        unsigned long midbits2 = lhs_high * rhs_low;
        unsigned long midbits  = midbits1 + midbits2;
        overflowed = overflowed || midbits < midbits1 || midbits > HALFSIZE_MAX;
        unsigned long product = lowbits + (midbits << LONG_BIT/2);
        overflowed = overflowed || product < lowbits;

        *result = product;
        return overflowed;
}

// Option 4, checks for overflow using division
//    - Checks for overflow using division
//    - Division is slow, especially if it is a library call

BOOL
umull_overflow4(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        *result = lhs * rhs;
        return rhs > 0 && (SIZE_MAX / rhs) < lhs;
}

// Option 5, checks for overflow using division
//    - Checks for overflow using division
//    - Avoids division when the numbers are "small enough" to trivially
//      rule out overflow
//    - Division is slow, especially if it is a library call

BOOL
umull_overflow5(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        const unsigned long MUL_NO_OVERFLOW = (1ul << LONG_BIT/2) - 1ul;
        *result = lhs * rhs;
        return (lhs >= MUL_NO_OVERFLOW || rhs >= MUL_NO_OVERFLOW) &&
            rhs > 0 && SIZE_MAX / rhs < lhs;
}

#ifndef umull_overflow
    #define umull_overflow2
#endif

/*
 * This benchmark code performs a multiply at all bit sizes, 
 * essentially assuming that sizes are logarithmically distributed.
 */

int main()
{
        unsigned long i, j, k;
        int count = 0;
        unsigned long mult;
        unsigned long total = 0;

        for (k = 0; k < 0x40000000 / LONG_BIT / LONG_BIT; ++k)
                for (i = 0; i != LONG_MAX; i = i*2+1)
                        for (j = 0; j != LONG_MAX; j = j*2+1) {
                                count += umull_overflow(i+k, j+k, &mult);
                                total += mult;
                        }
        printf("%d overflows (total %lu)n", count, total);
}

Here are the results, testing with various compilers and systems I have (in this case, all testing was done on OS X, but results should be similar on BSD or Linux systems):

+------------------+----------+----------+----------+----------+----------+
|                  | Option 1 | Option 2 | Option 3 | Option 4 | Option 5 |
|                  |  BigInt  | LngMult1 | LngMult2 |   Div    |  OptDiv  |
+------------------+----------+----------+----------+----------+----------+
| Clang 3.5 i386   |    1.610 |    3.217 |    3.129 |    4.405 |    4.398 |
| GCC 4.9.0 i386   |    1.488 |    3.469 |    5.853 |    4.704 |    4.712 |
| GCC 4.2.1 i386   |    2.842 |    4.022 |    3.629 |    4.160 |    4.696 |
| GCC 4.2.1 PPC32  |    8.227 |    7.756 |    7.242 |   20.632 |   20.481 |
| GCC 3.3   PPC32  |    5.684 |    9.804 |   11.525 |   21.734 |   22.517 |
+------------------+----------+----------+----------+----------+----------+
| Clang 3.5 x86_64 |    1.584 |    2.472 |    2.449 |    9.246 |    7.280 |
| GCC 4.9 x86_64   |    1.414 |    2.623 |    4.327 |    9.047 |    7.538 |
| GCC 4.2.1 x86_64 |    2.143 |    2.618 |    2.750 |    9.510 |    7.389 |
| GCC 4.2.1 PPC64  |   13.178 |    8.994 |    8.567 |   37.504 |   29.851 |
+------------------+----------+----------+----------+----------+----------+

Based on these results, we can draw a few conclusions:

  • Clearly, the division-based approach, although simple and portable, is slow.
  • No technique is a clear winner in all cases.
  • On modern compilers, the use-a-larger-int approach is best, if you can use it
  • On older compilers, the long-multiplication approach is best
  • Surprisingly, GCC 4.9.0 has performance regressions over GCC 4.2.1, and GCC 4.2.1 has performance regressions over GCC 3.3

answered Oct 12, 2014 at 0:33

Charphacy's user avatar

CharphacyCharphacy

2,0201 gold badge20 silver badges12 bronze badges

A version that also works when a == 0:

    x = a * b;
    if (a != 0 && x / a != b) {
        // overflow handling
    }

answered Nov 29, 2009 at 12:28

Mark Byers's user avatar

Mark ByersMark Byers

795k188 gold badges1562 silver badges1443 bronze badges

4

Easy and fast with clang and gcc:

unsigned long long t a, b, result;
if (__builtin_umulll_overflow(a, b, &result)) {
    // overflow!!
}

This will use hardware support for overflow detection where available. By being compiler extensions it can even handle signed integer overflow (replace umul with smul), eventhough that is undefined behavior in C++.

answered Jul 29, 2019 at 20:12

Allan Jensen's user avatar

0

If you need not just to detect overflow but also to capture the carry, you’re best off breaking your numbers down into 32-bit parts. The code is a nightmare; what follows is just a sketch:

#include <stdint.h>

uint64_t mul(uint64_t a, uint64_t b) {
  uint32_t ah = a >> 32;
  uint32_t al = a;  // truncates: now a = al + 2**32 * ah
  uint32_t bh = b >> 32;
  uint32_t bl = b;  // truncates: now b = bl + 2**32 * bh
  // a * b = 2**64 * ah * bh + 2**32 * (ah * bl + bh * al) + al * bl
  uint64_t partial = (uint64_t) al * (uint64_t) bl;
  uint64_t mid1    = (uint64_t) ah * (uint64_t) bl;
  uint64_t mid2    = (uint64_t) al * (uint64_t) bh;
  uint64_t carry   = (uint64_t) ah * (uint64_t) bh;
  // add high parts of mid1 and mid2 to carry
  // add low parts of mid1 and mid2 to partial, carrying
  //    any carry bits into carry...
}

The problem is not just the partial products but the fact that any of the sums can overflow.

If I had to do this for real, I would write an extended-multiply routine in the local assembly language. That is, for example, multiply two 64-bit integers to get a 128-bit result, which is stored in two 64-bit registers. All reasonable hardware provides this functionality in a single native multiply instruction—it’s not just accessible from C.

This is one of those rare cases where the solution that’s most elegant and easy to program is actually to use assembly language. But it’s certainly not portable :-(

answered Nov 30, 2009 at 1:29

Norman Ramsey's user avatar

Norman RamseyNorman Ramsey

197k59 gold badges359 silver badges531 bronze badges

2

The GNU Portability Library (Gnulib) contains a module intprops, which has macros that efficiently test whether arithmetic operations would overflow.

For example, if an overflow in multiplication would occur, INT_MULTIPLY_OVERFLOW (a, b) would yield 1.

answered Oct 11, 2019 at 20:58

Marc's user avatar

MarcMarc

4,3074 gold badges30 silver badges46 bronze badges

Perhaps the best way to solve this problem is to have a function, which multiplies two UInt64 and results a pair of UInt64, an upper part and a lower part of the UInt128 result. Here is the solution, including a function, which displays the result in hex. I guess you perhaps prefer a C++ solution, but I have a working Swift-Solution which shows, how to manage the problem:

func hex128 (_ hi: UInt64, _ lo: UInt64) -> String
{
    var s: String = String(format: "%08X", hi >> 32)
                  + String(format: "%08X", hi & 0xFFFFFFFF)
                  + String(format: "%08X", lo >> 32)
                  + String(format: "%08X", lo & 0xFFFFFFFF)
    return (s)
}

func mul64to128 (_ multiplier: UInt64, _ multiplicand : UInt64)
             -> (result_hi: UInt64, result_lo: UInt64)
{
    let x: UInt64 = multiplier
    let x_lo: UInt64 = (x & 0xffffffff)
    let x_hi: UInt64 = x >> 32

    let y: UInt64 = multiplicand
    let y_lo: UInt64 = (y & 0xffffffff)
    let y_hi: UInt64 = y >> 32

    let mul_lo: UInt64 = (x_lo * y_lo)
    let mul_hi: UInt64 = (x_hi * y_lo) + (mul_lo >> 32)
    let mul_carry: UInt64 = (x_lo * y_hi) + (mul_hi & 0xffffffff)
    let result_hi: UInt64 = (x_hi * y_hi) + (mul_hi >> 32) + (mul_carry >> 32)
    let result_lo: UInt64 = (mul_carry << 32) + (mul_lo & 0xffffffff)

    return (result_hi, result_lo)
}

Here is an example to verify, that the function works:

var c: UInt64 = 0
var d: UInt64 = 0

(c, d) = mul64to128(0x1234567890123456, 0x9876543210987654)
// 0AD77D742CE3C72E45FD10D81D28D038 is the result of the above example
print(hex128(c, d))

(c, d) = mul64to128(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF)
// FFFFFFFFFFFFFFFE0000000000000001 is the result of the above example
print(hex128(c, d))

answered Feb 23, 2017 at 21:55

j.s.com's user avatar

j.s.comj.s.com

1,38212 silver badges23 bronze badges

0

There is a simple (and often very fast solution) which has not been mentioned yet. The solution is based on the fact that n-Bit times m-Bit multiplication does never overflow for a product width of n+m-bit or higher but overflows for all result widths smaller than n+m-1.

Because my old description might have been too difficult to read for some people, I try it again:
What you need is checking the sum of leading-zeroes of both operands. It would be very easy to prove mathematically.
Let x be n-Bit and y be m-Bit. z = x * y is k-Bit. Because the product can be n+m bit large at most it can overflow. Let’s say. x*y is p-Bit long (without leading zeroes). The leading zeroes of the product are clz(x * y) = n+m - p. clz behaves similar to log, hence:
clz(x * y) = clz(x) + clz(y) + c with c = either 1 or 0.
(thank you for the c = 1 advice in the comment!)
It overflows when k < p <= n+m <=> n+m - k > n+m - p = clz(x * y).

Now we can use this algorithm:

if max(clz(x * y)) = clz(x) + clz(y) +1 < (n+m - k)  --> overflow
if max(clz(x * y)) = clz(x) + clz(y) +1 == (n+m - k)  --> overflow if c = 0
else --> no overflow

How to check for overflow in the middle case? I assume, you have a multiplication instruction. Then we easily can use it to see the leading zeroes of the result, i.e.:

if clz(x * y / 2) == (n+m - k) <=> msb(x * y/2) == 1  --> overflow
else --> no overflow

You do the multiplication by treating x/2 as fixed point and y as normal integer:

msb(x * y/2) = msb(floor(x * y / 2))
floor(x * y/2) = floor(x/2) * y + (lsb(x) * floor(y/2)) = (x >> 1)*y + (x & 1)*(y >> 1)

(this result never overflows in case of clz(x)+clz(y)+1 == (n+m -k))

The trick is using builtins/intrinsics. In GCC it looks this way:

static inline int clz(int a) {
    if (a == 0) return 32; //only needed for x86 architecture
    return __builtin_clz(a);
}
/**@fn static inline _Bool chk_mul_ov(uint32_t f1, uint32_t f2)
 * @return one, if a 32-Bit-overflow occurs when unsigned-unsigned-multipliying f1 with f2 otherwise zero. */
static inline _Bool chk_mul_ov(uint32_t f1, uint32_t f2) {
    int lzsum = clz(f1) + clz(f2); //leading zero sum
    return
        lzsum < sizeof(f1)*8-1 || ( //if too small, overflow guaranteed
            lzsum == sizeof(f1)*8-1 && //if special case, do further check
            (int32_t)((f1 >> 1)*f2 + (f1 & 1)*(f2 >> 1)) < 0 //check product rightshifted by one
    );
}
...
    if (chk_mul_ov(f1, f2)) {
        //error handling
    }
...

Just an example for n = m = k = 32-Bit unsigned-unsigned-multiplication. You can generalize it to signed-unsigned- or signed-signed-multiplication. And even no multiple-bit-shift is required (because some microcontrollers implement one-bit-shifts only but sometimes support product divided by two with a single instruction like Atmega!). However, if no count-leading-zeroes instruction exists but long multiplication, this might not be better.

Other compilers have their own way of specifying intrinsics for CLZ operations.
Compared to checking upper half of the multiplication the clz-method should scale better (in worst case) than using a highly optimized 128-Bit multiplication to check for 64-Bit overflow. Multiplication needs over linear overhead while count bits needs only linear overhead.
This code worked out-of-the box for me when tried.

answered Nov 29, 2019 at 18:20

ChrisoLosoph's user avatar

2

I’ve been working with this problem this days and I have to say that it has impressed me the number of times I have seen people saying the best way to know if there has been an overflow is to divide the result, thats totally inefficient and unnecessary. The point for this function is that it must be as fast as possible.

There are two options for the overflow detection:

1º- If possible create the result variable twice as big as the multipliers, for example:

struct INT32struct {INT16 high, low;};
typedef union
{
  struct INT32struct s;
  INT32 ll;
} INT32union;

INT16 mulFunction(INT16 a, INT16 b)
{
  INT32union result.ll = a * b; //32Bits result
  if(result.s.high > 0) 
      Overflow();
  return (result.s.low)
}

You will know inmediately if there has been an overflow, and the code is the fastest possible without writing it in machine code. Depending on the compiler this code can be improved in machine code.

2º- Is impossible to create a result variable twice as big as the multipliers variable:
Then you should play with if conditions to determine the best path. Continuing with the example:

INT32 mulFunction(INT32 a, INT32 b)
{

  INT32union s_a.ll = abs(a);
  INT32union s_b.ll = abs(b); //32Bits result
  INT32union result;
  if(s_a.s.hi > 0 && s_b.s.hi > 0)
  {
      Overflow();
  }
  else if (s_a.s.hi > 0)
  {
      INT32union res1.ll = s_a.s.hi * s_b.s.lo;
      INT32union res2.ll = s_a.s.lo * s_b.s.lo;
      if (res1.hi == 0)
      {
          result.s.lo = res1.s.lo + res2.s.hi;
          if (result.s.hi == 0)
          {
            result.s.ll = result.s.lo << 16 + res2.s.lo;
            if ((a.s.hi >> 15) ^ (b.s.hi >> 15) == 1)
            {
                result.s.ll = -result.s.ll; 
            }
            return result.s.ll
          }else
          {
             Overflow();
          }
      }else
      {
          Overflow();
      }
  }else if (s_b.s.hi > 0)
{

   //Same code changing a with b

}else 
{
    return (s_a.lo * s_b.lo);
}
}

I hope this code helps you to have a quite efficient program and I hope the code is clear, if not I’ll put some coments.

best regards.

answered Mar 11, 2013 at 19:34

user1368116's user avatar

3

Here is a trick for detecting whether multiplication of two unsigned integers overflows.

We make the observation that if we multiply an N-bit-wide binary number with an M-bit-wide binary number, the product does not have more than N + M bits.

For instance, if we are asked to multiply a three-bit number with a twenty-nine bit number, we know that this doesn’t overflow thirty-two bits.

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

int might_be_mul_oflow(unsigned long a, unsigned long b)
{
  if (!a || !b)
    return 0;

  a = a | (a >> 1) | (a >> 2) | (a >> 4) | (a >> 8) | (a >> 16) | (a >> 32);
  b = b | (b >> 1) | (b >> 2) | (b >> 4) | (b >> 8) | (b >> 16) | (b >> 32);

  for (;;) {
    unsigned long na = a << 1;
    if (na <= a)
      break;
    a = na;
  }

  return (a & b) ? 1 : 0;
}

int main(int argc, char **argv)
{
  unsigned long a, b;
  char *endptr;

  if (argc < 3) {
    printf("supply two unsigned long integers in C formn");
    return EXIT_FAILURE;
  }

  a = strtoul(argv[1], &endptr, 0);

  if (*endptr != 0) {
    printf("%s is garbagen", argv[1]);
    return EXIT_FAILURE;
  }

  b = strtoul(argv[2], &endptr, 0);

  if (*endptr != 0) {
    printf("%s is garbagen", argv[2]);
    return EXIT_FAILURE;
  }

  if (might_be_mul_oflow(a, b))
    printf("might be multiplication overflown");

  {
    unsigned long c = a * b;
    printf("%lu * %lu = %lun", a, b, c);
    if (a != 0 && c / a != b)
      printf("confirmed multiplication overflown");
  }

  return 0;
}

A smattering of tests: (on 64 bit system):

$ ./uflow 0x3 0x3FFFFFFFFFFFFFFF
3 * 4611686018427387903 = 13835058055282163709

$ ./uflow 0x7 0x3FFFFFFFFFFFFFFF
might be multiplication overflow
7 * 4611686018427387903 = 13835058055282163705
confirmed multiplication overflow

$ ./uflow 0x4 0x3FFFFFFFFFFFFFFF
might be multiplication overflow
4 * 4611686018427387903 = 18446744073709551612

$ ./uflow 0x5 0x3FFFFFFFFFFFFFFF
might be multiplication overflow
5 * 4611686018427387903 = 4611686018427387899
confirmed multiplication overflow

The steps in might_be_mul_oflow are almost certainly slower than just doing the division test, at least on mainstream processors used in desktop workstations, servers and mobile devices. On chips without good division support, it could be useful.


It occurs to me that there is another way to do this early rejection test.

  1. We start with a pair of numbers arng and brng which are initialized to 0x7FFF...FFFF and 1.

  2. If a <= arng and b <= brng we can conclude that there is no overflow.

  3. Otherwise, we shift arng to the right, and shift brng to the left, adding one bit to brng, so that they are 0x3FFF...FFFF and 3.

  4. If arng is zero, finish; otherwise repeat at 2.

The function now looks like:

int might_be_mul_oflow(unsigned long a, unsigned long b)
{
  if (!a || !b)
    return 0;

  {
    unsigned long arng = ULONG_MAX >> 1;
    unsigned long brng = 1;

    while (arng != 0) {
      if (a <= arng && b <= brng)
        return 0;
      arng >>= 1;
      brng <<= 1;
      brng |= 1;
    }

    return 1;
  }
}

answered Jun 12, 2017 at 18:42

Kaz's user avatar

KazKaz

54.1k9 gold badges97 silver badges145 bronze badges

When your using e.g. 64 bits variables, implement ‘number of significant bits’ with nsb(var) = { 64 — clz(var); }.

clz(var) = count leading zeros in var, a builtin command for GCC and Clang, or probably available with inline assembly for your CPU.

Now use the fact that nsb(a * b) <= nsb(a) + nsb(b) to check for overflow. When smaller, it is always 1 smaller.

Ref GCC: Built-in Function: int __builtin_clz (unsigned int x)
Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined.

answered Jan 11, 2021 at 7:31

Steven Buytaert's user avatar

I was thinking about this today and stumbled upon this question, my thoughts led me to this result. TLDR, while I find it «elegant» in that it only uses a few lines of code (could easily be a one liner), and has some mild math that simplifies to something relatively simple conceptually, this is mostly «interesting» and I haven’t tested it.

If you think of an unsigned integer as being a single digit with radix 2^n where n is the number of bits in the integer, then you can map those numbers to radians around the unit circle, e.g.

radians(x) = x * (2 * pi * rad / 2^n)

When the integer overflows, it is equivalent to wrapping around the circle. So calculating the carry is equivalent to calculating the number of times multiplication would wrap around the circle. To calculate the number of times we wrap around the circle we divide radians(x) by 2pi radians. e.g.

wrap(x) = radians(x) / (2*pi*rad)
        = (x * (2*pi*rad / 2^n)) / (2*pi*rad / 1)
        = (x * (2*pi*rad / 2^n)) * (1 / 2*pi*rad)
        = x * 1 / 2^n
        = x / 2^n

Which simplifies to

wrap(x) = x / 2^n

This makes sense. The number of times a number, for example, 15 with radix 10, wraps around is 15 / 10 = 1.5, or one and a half times. However, we can’t use 2 digits here (assuming we are limited to a single 2^64 digit).

Say we have a * b, with radix R, we can calculate the carry with

Consider that: wrap(a * b) = a * wrap(b)
wrap(a * b) = (a * b) / R
a * wrap(b) = a * (b / R)
a * (b / R) = (a * b) / R

carry = floor(a * wrap(b))

Take for example a = 9 and b = 5, which are factors of 45 (i.e. 9 * 5 = 45).

wrap(5) = 5 / 10 = 0.5
a * wrap(5) = 9 * 0.5 = 4.5
carry = floor(9 * wrap(5)) = floor(4.5) = 4

Note that if the carry was 0, then we would not have had overflow, for example if a = 2, b=2.

In C/C++ (if the compiler and architecture supports it) we have to use long double.

Thus we have:

long double wrap = b / 18446744073709551616.0L; // this is b / 2^64
unsigned long carry = (unsigned long)(a * wrap); // floor(a * wrap(b))
bool overflow = carry > 0;
unsigned long c = a * b;

c here is the lower significant «digit», i.e. in base 10 9 * 9 = 81, carry = 8, and c = 1.

This was interesting to me in theory, so I thought I’d share it, but one major caveat is with the floating point precision in computers. Using long double, there may be rounding errors for some numbers when we calculate the wrap variable depending on how many significant digits your compiler/arch uses for long doubles, I believe it should be 20 more more to be sure. Another issue with this result, is that it may not perform as well as some of the other solutions simply by using floating points and division.

answered Apr 19, 2021 at 20:14

Todd Fulton's user avatar

If you just want to detect overflow, how about converting to double, doing the multiplication and if

|x| < 2^53, convert to int64

|x| < 2^63, make the multiplication using int64

otherwise produce whatever error you want?

This seems to work:

int64_t safemult(int64_t a, int64_t b) {
  double dx;

  dx = (double)a * (double)b;

  if ( fabs(dx) < (double)9007199254740992 )
    return (int64_t)dx;

  if ( (double)INT64_MAX < fabs(dx) )
    return INT64_MAX;

  return a*b;
}

answered Oct 13, 2017 at 15:28

Gunnar Thorburn's user avatar

1

Цель работы:
1) изучить менеджер памяти С++
2) изучить различные способы обработки исключений;
3) получить практические навыки программирования задач с выделением памяти и обработкой исключений.

Теоретические сведения

Выделение памяти

В языке программирования C++ оператор new обеспечивает выделение динамической памяти в куче.
За исключением формы, называемой «размещающей формой new», new пытается выделить достаточно памяти в куче для размещения новых данных и,
в случае успеха, возвращает адрес свежевыделенной памяти.
Однако, если new не может выделить память в куче, то он генерирует (throw) исключение типа std::bad_alloc.
Это устраняет необходимость явной проверки результата выделения.

Синтаксис new выглядит следующим образом:
p_var = new typename;

где p_var — ранее объявленный указатель типа typename. typename может подразумевать собой любой фундаментальный тип данных или объект, определенный пользователем (включая, enum, class и struct). Если typename — это тип класса или структуры, то он должен иметь доступный конструктор по умолчанию, который будет вызван для создания объекта.

Для инициализации новой переменной, созданной при помощи new нужно использовать следующий синтаксис:
p_var = new type(initializer);

где initializer — первоначальное значение, присвоенное новой переменной, а если type — тип класса, то initializer — аргумент(ы) конструктора.

new может также создавать массив:
p_var = new type [size];

В данном случае, size указывает размерность (длину) создаваемого одномерного массива. Адрес первого элемента возвращается и помещается в p_var, поэтому
p_var[n]

означает значение n-го элемента (считая от нулевой позиции)

Память, выделенная при помощи new, должна быть освобождена при помощи delete, дабы избежать утечки памяти.
Массивы, выделенные (созданные) при помощи new[], должны освобождаться (уничтожаться) при помощи delete[].

Инициализаторы не могут быть указаны для массивов, созданных при помощи new. Все элементы массива инициализируются при помощи конструктора по умолчанию для данного типа. Если тип не имеет конструктора по умолчанию, выделенная область памяти не будет проинициализирована.

Существует особая форма оператора new, называемая placement new.
Данный оператор не выделяет память, а получает своим аргументом адрес на уже выделенную каким-либо образом память (например, на стеке или через malloc).
Происходит размещение (инициализация) объекта путем вызова конструктора, и объект создается в памяти по указанному адресу.
Часто такой метод применяют, когда у класса нет конструктора по умолчанию и при этом нужно создать массив объектов.
Пример вызова выглядит следующим образом:

Поскольку при выделении памяти тип создаваемого объекта(ов) не был указан, компилятор не будет вызывать деструктор для каждого объекта массива, поэтому это нужно сделать вручную, перед освобождением блока памяти.
Проверка выделения памяти

В компиляторах, придерживающихся стандарта ISO C++, в случае если недостаточно памяти для выделения, то генерируется исключение типа std::bad_alloc. Выполнение всего последующего кода прекращается, пока ошибка не будет обработана в блоке try-catch или произойдет экстренное завершение программы. Программа не нуждается в проверке значения указателя; если не было сгенерировано исключение, то выделение прошло успешно. Реализованные операции определяются в заголовке . В большинстве реализаций C++ оператор new также может быть перегружен для определения особого поведения.
Освобождение памяти

В языке программирования C++ оператор delete (или delete[]) возвращает память, выделенную оператором new, обратно в кучу. Вызов delete должен происходить для каждого вызова new, чтобы избежать утечки памяти. После вызова delete объект, указывающий на этот участок памяти, становится некорректным и не должен больше использоваться. Многие программисты присваивают 0 (нуль-указатель) указателям после использования delete, чтобы минимизировать количество ошибок программирования. Однако нужно отметить, что удаление нуль-указателя фактически не имеет эффекта, так что нет необходимости проверять нуль-указатель перед вызовом delete.

Фрагмент кода в качестве примера:

Вызов delete[] для массива объектов приведет к вызову деструктора для каждого объекта перед освобождением памяти, выделенной под массив.

Исключения — возникновение непредвиденных ошибочных ситуаций, например деление на ноль при операциях с плавающей точкой. Обычно эти условия завершают программу пользователя с системным сообщением об ошибке. Обработка исключений в С++ дает возможность программисту восстанавливать программу из этих условий и продолжать ее выполнение.
Исключения в C++
Язык С++ имеет чувствительный к контексту механизм обработки особых ситуаций.
Контекст для установки исключения — это блок try.
Обработчики объявлены в конце блока try с использованием ключевого слова catch.
Простой пример:

Установленные исключения

Синтаксически выражения throw возникает в двух формах:

Выражение throw устанавливает исключение. Выражение throw без аргумента повторно устанавливает текущее исключение. Обычно оно используется, когда для дальнейшей обработки исключения необходим второй обработчик, вызываемый из первого.

Если пользователь хочет выводить дополнительную информацию или использовать ее для принятия решения относительно действий обработчика, то допустимо формирование в виде объекта.

Теперь выражение throw может быть более информативным


throw vect_error(bounds, i, ub);

Блоки try
Синтаксически блок try имеет такую форму
try
составной оператор
список обработчиков
Блок try — контекст для принятия решения о том, какие обработчики вызываются для установленного исключения.

Выражение throw соответствует аргументу catch, если он:
точно соответствует.
общий базовый класс порожденного типа представляет собой то, что устанавливается.
объект установленного типа является типом указателя, преобразуемым в тип указателя, являющегося аргументом catch.
Обработчики catch
Синтаксически обработчик catch имеет следующую форму

Спецификация исключения
Синтаксис
Заголовок_функции throw(список типов)

void foo() throw(int, over_flow);
void noex(int i) throw();
Terminate() и unexpected()
Обработчик terminate() вызывается, когда для обработки исключения не поставлен другой обработчик. По умолчанию вызывается функция abort().
Обработчик unexpected() вызывается, когда исключения не было в списке спецификации исключения

Пример кода, реализующего исключение
Пример 1.

Файл vect.h:

Контрольные вопросы

  1. Какую цель преследует использование в программе обработки исключений?
  2. Как оформляется блок обработки исключений?
  3. Что такое обработчики исключений?

Варианты заданий

  1. Опишите функцию умножения двух целых, обработайте ошибку переполнения сверху (overflow).
  2. Опишите функцию деления двух целых, обработайте ошибку переполнения снизу (underflow).
  3. Опишите функцию деления двух целых, обработайте ошибку деления на ноль (zero division).
  4. Переопределите оператор ++ для указателя на массив целых, обработайте ошибку выхода за границы массива.
  5. Опишите функцию анализа номера телефона, обработайте ошибку задания номера в неверном формате (допустимый формат — +7(095)555-44-33).
  6. Опишите оператор [] для списка элементов, обработайте ошибку выхода за границы массива.
  7. Опишите функцию, возвращающую день недели по дню и месяцу, обработайте ошибки неверного дня или месяца.
  8. Опишите функцию умножения двух чисел с плавающей запятой, обработайте ошибку переполнения сверху (overflow).
  9. Опишите функцию деления двух чисел с плавающей запятой, обработайте ошибку переполнения снизу (underflow).
  10. Опишите функцию деления двух чисел с плавающей запятой, обработайте ошибку деления на ноль (zero division).
  11. Опишите оператор [] для очереди элементов, обработайте ошибку выхода за границы массива.
  12. Опишите оператор [] для вектора элементов, обработайте ошибку выхода за границы массива.
  13. Реализуйте иерархию классов «MathErr», обрабатывающих ошибки переполнения сверху/снизу и деления на ноль.
<!DOCTYPE html> <html langru«> <head> <meta charsetutf-8» /> <meta http-equivcontent-type» contenttext/html; charset=utf-8«/> <meta http-equivContent-Style-Type» contenttext/css«/> <meta nameviewport» contentwidth=device-width«/> <link href/js/codemirror/lib/codemirror.css» relstylesheet» typetext/css» /> <link href/js/codemirror/theme/default.css» relstylesheet» typetext/css» /> <script src/js/codemirror/lib/codemirror.js» typetext/javascript«></script> <script src/js/codemirror/mode/clike/clike.js» typetext/javascript«></script> <script typetext/javascript» src/js/jquery.js«></script> <script typetext/javascript» src/js/main.js«></script> <link relstylesheet» typetext/css» href/styles.css» /> <title>Лабораторная работа №4</title> </head> <body> <h1>Лабораторная работа №4: Динамическое выделение памяти и исключения в С++</h1> <p>Цель работы: 1) изучить менеджер памяти С++ 2) изучить различные способы обработки исключений; 3) получить практические навыки программирования задач с выделением памяти и обработкой исключений. Теоретические сведения <h2>Выделение памяти</h2> <p>В языке программирования C++ оператор new обеспечивает выделение динамической памяти в куче. За исключением формы, называемой «размещающей формой new», new пытается выделить достаточно памяти в куче для размещения новых данных и, в случае успеха, возвращает адрес свежевыделенной памяти. Однако, если new не может выделить память в куче, то он генерирует (throw) исключение типа <dfn>std::bad_alloc</dfn>. Это устраняет необходимость явной проверки результата выделения.</p> <p>Синтаксис new выглядит следующим образом: p_var = new typename; где p_var — ранее объявленный указатель типа typename. typename может подразумевать собой любой фундаментальный тип данных или объект, определенный пользователем (включая, enum, class и struct). Если typename — это тип класса или структуры, то он должен иметь доступный конструктор по умолчанию, который будет вызван для создания объекта. Для инициализации новой переменной, созданной при помощи new нужно использовать следующий синтаксис: p_var = new type(initializer); где initializer — первоначальное значение, присвоенное новой переменной, а если type — тип класса, то initializer — аргумент(ы) конструктора. new может также создавать массив: p_var = new type [size]; В данном случае, size указывает размерность (длину) создаваемого одномерного массива. Адрес первого элемента возвращается и помещается в p_var, поэтому p_var[n] означает значение n-го элемента (считая от нулевой позиции) <p>Память, выделенная при помощи new, должна быть освобождена при помощи delete, дабы избежать утечки памяти. Массивы, выделенные (созданные) при помощи new[], должны освобождаться (уничтожаться) при помощи delete[].</p> <textarea classcpp» readonly> int *p_scalar = new int(5); int *p_array = new int[5]; </textarea> <p>Инициализаторы не могут быть указаны для массивов, созданных при помощи new. Все элементы массива инициализируются при помощи конструктора по умолчанию для данного типа. Если тип не имеет конструктора по умолчанию, выделенная область памяти не будет проинициализирована. <p>Существует особая форма оператора new, называемая <strong>placement new</strong>. Данный оператор не выделяет память, а получает своим аргументом адрес на уже выделенную каким-либо образом память (например, на стеке или через malloc). Происходит размещение (инициализация) объекта путем вызова конструктора, и объект создается в памяти по указанному адресу. Часто такой метод применяют, когда у класса нет конструктора по умолчанию и при этом нужно создать массив объектов. Пример вызова выглядит следующим образом:</p> <textarea classcpp» readonly> class A { public: A(int x){} ~A(){} }; const int n = 50; A* placementMemory = static_cast<A*>(operator new[] (n * sizeof(A))); for (int i = 0; i < n; i++) { new (placementMemory + i) A(rand()); //здесь память для объекта не выделяется, но инициализируется } //!!деинициализация памяти for (int i = 0; i < n; i++) { placementMemory[i].~A(); } operator delete[] (placementMemory); </textarea> <p>Поскольку при выделении памяти тип создаваемого объекта(ов) не был указан, компилятор не будет вызывать деструктор для каждого объекта массива, поэтому это нужно сделать вручную, перед освобождением блока памяти. Проверка выделения памяти В компиляторах, придерживающихся стандарта ISO C++, в случае если недостаточно памяти для выделения, то генерируется исключение типа std::bad_alloc. Выполнение всего последующего кода прекращается, пока ошибка не будет обработана в блоке try-catch или произойдет экстренное завершение программы. Программа не нуждается в проверке значения указателя; если не было сгенерировано исключение, то выделение прошло успешно. Реализованные операции определяются в заголовке <new>. В большинстве реализаций C++ оператор new также может быть перегружен для определения особого поведения. Освобождение памяти В языке программирования C++ оператор delete (или delete[]) возвращает память, выделенную оператором new, обратно в кучу. Вызов delete должен происходить для каждого вызова new, чтобы избежать утечки памяти. После вызова delete объект, указывающий на этот участок памяти, становится некорректным и не должен больше использоваться. Многие программисты присваивают 0 (нуль-указатель) указателям после использования delete, чтобы минимизировать количество ошибок программирования. Однако нужно отметить, что удаление нуль-указателя фактически не имеет эффекта, так что нет необходимости проверять нуль-указатель перед вызовом delete. Фрагмент кода в качестве примера:</p> <textarea classcpp» readonly> int *p_var = NULL; // объявление нового указателя p_var = new int; // память динамически выделяется /* ……. остальной код ……..*/ delete p_var; // память освобождается p_var = NULL; // указатель заменяется на 0 (нуль-указатель) Массивы, созданные (выделенные) при помощи new [], аналогичным образом должны быть уничтожены (оcвобождены) при помощи delete []: int size = 10; int *p_var = NULL; // объявление нового указателя p_var = new int [size];// память динамически выделяется /* ……. остальной код ……..*/ delete [] p_var; // память освобождается p_var = NULL; // указатель заменяется на 0 (нуль-указатель) </textarea> <p>Вызов delete[] для массива объектов приведет к вызову деструктора для каждого объекта перед освобождением памяти, выделенной под массив. Исключения — возникновение непредвиденных ошибочных ситуаций, например деление на ноль при операциях с плавающей точкой. Обычно эти условия завершают программу пользователя с системным сообщением об ошибке. Обработка исключений в С++ дает возможность программисту восстанавливать программу из этих условий и продолжать ее выполнение. Исключения в C++ Язык С++ имеет чувствительный к контексту механизм обработки особых ситуаций. Контекст для установки исключения — это блок try. Обработчики объявлены в конце блока try с использованием ключевого слова catch. Простой пример:</p> <textarea classcpp» readonly> vect::vect(int n) { if (n < 1) throw(n); p = new int[n]; if (p == 0) throw(«FREE STORE EXHAUSTED»); } void g() { try { vect a(n), b(n); … } catch(int n) { … } //отслеживает все неправильные размеры catch(char* error) {…} //отслеживает превышение свободной памяти } </textarea> <h2>Установленные исключения</h2> <p>Синтаксически выражения throw возникает в двух формах:</p> <textarea classcpp» readonly> throw; throw выражение; </textarea> <p>Выражение throw устанавливает исключение. Выражение throw без аргумента повторно устанавливает текущее исключение. Обычно оно используется, когда для дальнейшей обработки исключения необходим второй обработчик, вызываемый из первого.</p> <textarea classcpp» readonly> void foo() { int i; … throw (i); } main() { try { foo(); } catch(int i) { … } } </textarea> <p>Если пользователь хочет выводить дополнительную информацию или использовать ее для принятия решения относительно действий обработчика, то допустимо формирование в виде объекта.</p> <textarea classcpp» readonly> enum error {bounds, heap, other}; class vect_error { private: error e_type; int ub, index, size; public: vect_error(error, int, int); //пакет вне заданных пределов vect_error(error, int); //пакет вне памяти } </textarea> <p>Теперь выражение throw может быть более информативным … throw vect_error(bounds, i, ub); … Блоки try Синтаксически блок try имеет такую форму try составной оператор список обработчиков Блок try — контекст для принятия решения о том, какие обработчики вызываются для установленного исключения. <textarea classcpp» readonly> try { … throw(«SOS»); … io_condition.eof(argv[i]); throw(eof); … } catch (const char*) {…} catch (io_condition& x) {…} </textarea> <p>Выражение throw соответствует аргументу catch, если он: точно соответствует. общий базовый класс порожденного типа представляет собой то, что устанавливается. объект установленного типа является типом указателя, преобразуемым в тип указателя, являющегося аргументом catch. Обработчики catch Синтаксически обработчик catch имеет следующую форму <textarea classcpp» readonly> catch (формальный аргумент) составной оператор catch (char* message) { cerr << message << endl; } catch (…) //действие по умолчанию { cerr << «THAT’S ALL FOLKS.» << endl; abort(); } </textarea> <p>Спецификация исключения Синтаксис Заголовок_функции throw(список типов) void foo() throw(int, over_flow); void noex(int i) throw(); Terminate() и unexpected() Обработчик terminate() вызывается, когда для обработки исключения не поставлен другой обработчик. По умолчанию вызывается функция abort(). Обработчик unexpected() вызывается, когда исключения не было в списке спецификации исключения <p>Пример кода, реализующего исключение Пример 1.</p> <textarea classcpp» readonly> #include «vect.h» void g(int n) { try { // блок try — контекст для принятия решения о том, какие // обработчики вызываются для установленного исключения vect a(n); } catch (int n) // обработчик { cerr << «SIZE ERROR » << n << endl; g(10); } catch (const char* error) // обработчик { cerr << error << endl; abort(); } catch (std::bad_alloc x) { cerr << “out of memory” << endl; } } int main() { extern void g(int n); g(-1); } </textarea> <p>Файл vect.h:</p> <textarea classcpp» readonly> #include <iostream.h> class vect { private: int* p; int size; public: vect() { size = 11; p = new int[size]; } vect(int n); ~vect() { delete [] p; } int& element(int i); int ub() const { return (size — 1); } }; vect::vect(int n) { if(n < 1) // оговоренное предусловие throw (n); // устанавливается исключение p = new int[n]; if(p == 0) // оговоренное постусловие throw («FREE STORE EXHAUSTED«); // устанавливается исключение для старых компиляторов } int& vect::element(int n) { if(n < 0 || n > size-1) throw («ILLEGAL NUMBER OF ELEMENT»); // устанавливается исключение return (p[n]); } </textarea> <h2>Контрольные вопросы</h2> <ol> <li>Какую цель преследует использование в программе обработки исключений? <li>Как оформляется блок обработки исключений? <li>Что такое обработчики исключений? </ol> <h2>Варианты заданий</h2> <ol> <li>Опишите функцию умножения двух целых, обработайте ошибку переполнения сверху (overflow). <li>Опишите функцию деления двух целых, обработайте ошибку переполнения снизу (underflow). <li>Опишите функцию деления двух целых, обработайте ошибку деления на ноль (zero division). <li>Переопределите оператор ++ для указателя на массив целых, обработайте ошибку выхода за границы массива. <li>Опишите функцию анализа номера телефона, обработайте ошибку задания номера в неверном формате (допустимый формат — +7(095)555-44-33). <li>Опишите оператор [] для списка элементов, обработайте ошибку выхода за границы массива. <li>Опишите функцию, возвращающую день недели по дню и месяцу, обработайте ошибки неверного дня или месяца. <li>Опишите функцию умножения двух чисел с плавающей запятой, обработайте ошибку переполнения сверху (overflow). <li>Опишите функцию деления двух чисел с плавающей запятой, обработайте ошибку переполнения снизу (underflow). <li>Опишите функцию деления двух чисел с плавающей запятой, обработайте ошибку деления на ноль (zero division). <li>Опишите оператор [] для очереди элементов, обработайте ошибку выхода за границы массива. <li>Опишите оператор [] для вектора элементов, обработайте ошибку выхода за границы массива. <li>Реализуйте иерархию классов «MathErr», обрабатывающих ошибки переполнения сверху/снизу и деления на ноль. </ol> </body> </html>

Уловить и вычислить переполнение при умножении двух больших целых чисел

Я ищу эффективное (необязательно стандартное, элегантное и простое в реализации) решение для умножения относительно больших чисел и сохранения результата в одно или несколько целых чисел:

допустим, у меня есть два 64-битных целых числа, объявленных так:

uint64_t a = xxx, b = yyy; 

когда я делаю a * b, Как я могу определить, приводит ли операция к переполнению и в этом случае хранить перенос где-нибудь?

обратите внимание:Я не хочу использовать какое-либо большое число библиотека так как у меня есть ограничения на способ хранения чисел.

9 ответов


1. Обнаружение переполнения:

x = a * b;
if (a != 0 && x / a != b) {
    // overflow handling
}

Edit: исправлено деление на 0 (спасибо Марк!)

2. Вычисление carry вполне участвует. Один из подходов-разделить оба операнда на полуслова, а затем применить длинное умножение до полуслова:

uint64_t hi(uint64_t x) {
    return x >> 32;
}

uint64_t lo(uint64_t x) {
    return ((1L << 32) - 1) & x;
}

void multiply(uint64_t a, uint64_t b) {
    // actually uint32_t would do, but the casting is annoying
    uint64_t s0, s1, s2, s3; 

    uint64_t x = lo(a) * lo(b);
    s0 = lo(x);

    x = hi(a) * lo(b) + hi(x);
    s1 = lo(x);
    s2 = hi(x);

    x = s1 + lo(a) * hi(b);
    s1 = lo(x);

    x = s2 + hi(a) * hi(b) + hi(x);
    s2 = lo(x);
    s3 = hi(x);

    uint64_t result = s1 << 32 | s0;
    uint64_t carry = s3 << 32 | s2;
}

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

        x = s2 + hi(a) * hi(b) + hi(x)

пусть B = 1 << 32. Мы тогда есть

            x <= (B - 1) + (B - 1)(B - 1) + (B - 1)
              <= B*B - 1
               < B*B

Я считаю, что это сработает — по крайней мере, он обрабатывает тестовый случай Sjlver. Кроме того, он непроверен (и может даже не компилироваться, поскольку у меня больше нет компилятора C++).


идея состоит в том, чтобы использовать следующий факт, который верен для интегральной операции:

a*b > c, если и только если a > c/b

/ здесь целочисленного деления.

псевдокод для проверки переполнения для положительных чисел следует:

if (a > max_int64/ b) затем «переполнение» else «ok».

для обработки нулей и отрицательных чисел вы должны добавить больше проверок.

C код для неотрицательных a и b следующее:

if (b > 0 && a > 18446744073709551615 / b) {
     // overflow handling
}; else {
    c = a * b;
}

Примечание:

18446744073709551615 == (1<<64)-1

для вычисления переноса мы можем использовать подход, чтобы разделить число на две 32-значные цифры и умножить их, как мы делаем это на бумаге. Нам нужно разделить числа, чтобы избежать переполнения.

код ниже:

// split input numbers into 32-bit digits
uint64_t a0 = a & ((1LL<<32)-1);
uint64_t a1 = a >> 32;
uint64_t b0 = b & ((1LL<<32)-1);
uint64_t b1 = b >> 32;


// The following 3 lines of code is to calculate the carry of d1
// (d1 - 32-bit second digit of result, and it can be calculated as d1=d11+d12),
// but to avoid overflow.
// Actually rewriting the following 2 lines:
// uint64_t d1 = (a0 * b0 >> 32) + a1 * b0 + a0 * b1;
// uint64_t c1 = d1 >> 32;
uint64_t d11 = a1 * b0 + (a0 * b0 >> 32); 
uint64_t d12 = a0 * b1;
uint64_t c1 = (d11 > 18446744073709551615 - d12) ? 1 : 0;

uint64_t d2 = a1 * b1 + c1;
uint64_t carry = d2; // needed carry stored here

хотя было несколько других ответов на этот вопрос, у некоторых из них есть код, который полностью непроверен, и до сих пор никто адекватно не сравнил различные возможные варианты.

по этой причине я написал и протестировал несколько возможных реализаций (последняя основана на код из OpenBSD, обсуждается на Reddit здесь). Вот код:

/* Multiply with overflow checking, emulating clang's builtin function
 *
 *     __builtin_umull_overflow
 *
 * This code benchmarks five possible schemes for doing so.
 */

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <limits.h>

#ifndef BOOL
    #define BOOL int
#endif

// Option 1, check for overflow a wider type
//    - Often fastest and the least code, especially on modern compilers
//    - When long is a 64-bit int, requires compiler support for 128-bits
//      ints (requires GCC >= 3.0 or Clang)

#if LONG_BIT > 32
    typedef __uint128_t long_overflow_t ;
#else
    typedef uint64_t long_overflow_t;
#endif

BOOL 
umull_overflow1(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        long_overflow_t prod = (long_overflow_t)lhs * (long_overflow_t)rhs;
        *result = (unsigned long) prod;
        return (prod >> LONG_BIT) != 0;
}

// Option 2, perform long multiplication using a smaller type
//    - Sometimes the fastest (e.g., when mulitply on longs is a library
//      call).
//    - Performs at most three multiplies, and sometimes only performs one.
//    - Highly portable code; works no matter how many bits unsigned long is

BOOL 
umull_overflow2(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        const unsigned long HALFSIZE_MAX = (1ul << LONG_BIT/2) - 1ul;
        unsigned long lhs_high = lhs >> LONG_BIT/2;
        unsigned long lhs_low  = lhs & HALFSIZE_MAX;
        unsigned long rhs_high = rhs >> LONG_BIT/2;
        unsigned long rhs_low  = rhs & HALFSIZE_MAX;

        unsigned long bot_bits = lhs_low * rhs_low;
        if (!(lhs_high || rhs_high)) {
            *result = bot_bits;
            return 0; 
        }
        BOOL overflowed = lhs_high && rhs_high;
        unsigned long mid_bits1 = lhs_low * rhs_high;
        unsigned long mid_bits2 = lhs_high * rhs_low;

        *result = bot_bits + ((mid_bits1+mid_bits2) << LONG_BIT/2);
        return overflowed || *result < bot_bits
            || (mid_bits1 >> LONG_BIT/2) != 0
            || (mid_bits2 >> LONG_BIT/2) != 0;
}

// Option 3, perform long multiplication using a smaller type (this code is
// very similar to option 2, but calculates overflow using a different but
// equivalent method).
//    - Sometimes the fastest (e.g., when mulitply on longs is a library
//      call; clang likes this code).
//    - Performs at most three multiplies, and sometimes only performs one.
//    - Highly portable code; works no matter how many bits unsigned long is

BOOL 
umull_overflow3(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        const unsigned long HALFSIZE_MAX = (1ul << LONG_BIT/2) - 1ul;
        unsigned long lhs_high = lhs >> LONG_BIT/2;
        unsigned long lhs_low  = lhs & HALFSIZE_MAX;
        unsigned long rhs_high = rhs >> LONG_BIT/2;
        unsigned long rhs_low  = rhs & HALFSIZE_MAX;

        unsigned long lowbits = lhs_low * rhs_low;
        if (!(lhs_high || rhs_high)) {
            *result = lowbits;
            return 0; 
        }
        BOOL overflowed = lhs_high && rhs_high;
        unsigned long midbits1 = lhs_low * rhs_high;
        unsigned long midbits2 = lhs_high * rhs_low;
        unsigned long midbits  = midbits1 + midbits2;
        overflowed = overflowed || midbits < midbits1 || midbits > HALFSIZE_MAX;
        unsigned long product = lowbits + (midbits << LONG_BIT/2);
        overflowed = overflowed || product < lowbits;

        *result = product;
        return overflowed;
}

// Option 4, checks for overflow using division
//    - Checks for overflow using division
//    - Division is slow, especially if it is a library call

BOOL
umull_overflow4(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        *result = lhs * rhs;
        return rhs > 0 && (SIZE_MAX / rhs) < lhs;
}

// Option 5, checks for overflow using division
//    - Checks for overflow using division
//    - Avoids division when the numbers are "small enough" to trivially
//      rule out overflow
//    - Division is slow, especially if it is a library call

BOOL
umull_overflow5(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        const unsigned long MUL_NO_OVERFLOW = (1ul << LONG_BIT/2) - 1ul;
        *result = lhs * rhs;
        return (lhs >= MUL_NO_OVERFLOW || rhs >= MUL_NO_OVERFLOW) &&
            rhs > 0 && SIZE_MAX / rhs < lhs;
}

#ifndef umull_overflow
    #define umull_overflow2
#endif

/*
 * This benchmark code performs a multiply at all bit sizes, 
 * essentially assuming that sizes are logarithmically distributed.
 */

int main()
{
        unsigned long i, j, k;
        int count = 0;
        unsigned long mult;
        unsigned long total = 0;

        for (k = 0; k < 0x40000000 / LONG_BIT / LONG_BIT; ++k)
                for (i = 0; i != LONG_MAX; i = i*2+1)
                        for (j = 0; j != LONG_MAX; j = j*2+1) {
                                count += umull_overflow(i+k, j+k, &mult);
                                total += mult;
                        }
        printf("%d overflows (total %lu)n", count, total);
}

здесь результаты, испытывая с различным компиляторы и системы у меня есть (в этом случае все тестирование проводилось на OS X, но результаты должны быть похожи на BSD или Linux):

+------------------+----------+----------+----------+----------+----------+
|                  | Option 1 | Option 2 | Option 3 | Option 4 | Option 5 |
|                  |  BigInt  | LngMult1 | LngMult2 |   Div    |  OptDiv  |
+------------------+----------+----------+----------+----------+----------+
| Clang 3.5 i386   |    1.610 |    3.217 |    3.129 |    4.405 |    4.398 |
| GCC 4.9.0 i386   |    1.488 |    3.469 |    5.853 |    4.704 |    4.712 |
| GCC 4.2.1 i386   |    2.842 |    4.022 |    3.629 |    4.160 |    4.696 |
| GCC 4.2.1 PPC32  |    8.227 |    7.756 |    7.242 |   20.632 |   20.481 |
| GCC 3.3   PPC32  |    5.684 |    9.804 |   11.525 |   21.734 |   22.517 |
+------------------+----------+----------+----------+----------+----------+
| Clang 3.5 x86_64 |    1.584 |    2.472 |    2.449 |    9.246 |    7.280 |
| GCC 4.9 x86_64   |    1.414 |    2.623 |    4.327 |    9.047 |    7.538 |
| GCC 4.2.1 x86_64 |    2.143 |    2.618 |    2.750 |    9.510 |    7.389 |
| GCC 4.2.1 PPC64  |   13.178 |    8.994 |    8.567 |   37.504 |   29.851 |
+------------------+----------+----------+----------+----------+----------+

основываясь на этих результатах, мы можем сделать несколько выводов:

  • очевидно, что подход на основе разделения, хотя простой и портативный, является медленным.
  • никакая техника не является явным победителем во всех случаях.
  • на современных компиляторах подход use-a-large-int лучше всего, если вы можете его использовать
  • On старые компиляторы, подход с длинным умножением лучше всего
  • удивительно, GCC 4.9.0 имеет регрессии производительности по GCC 4.2.1, а GCC 4.2.1 имеет регрессии производительности по GCC 3.3

версия, которая также работает, когда a == 0:

    x = a * b;
    if (a != 0 && x / a != b) {
        // overflow handling
    }

Если вам нужно не только обнаружить переполнение, но и захватить перенос, вам лучше разбить свои номера на 32-битные части. Код-это кошмар; то, что следует ниже, — всего лишь набросок:

#include <stdint.h>

uint64_t mul(uint64_t a, uint64_t b) {
  uint32_t ah = a >> 32;
  uint32_t al = a;  // truncates: now a = al + 2**32 * ah
  uint32_t bh = b >> 32;
  uint32_t bl = b;  // truncates: now b = bl + 2**32 * bh
  // a * b = 2**64 * ah * bh + 2**32 * (ah * bl + bh * al) + al * bl
  uint64_t partial = (uint64_t) al * (uint64_t) bl;
  uint64_t mid1    = (uint64_t) ah * (uint64_t) bl;
  uint64_t mid2    = (uint64_t) al * (uint64_t) bh;
  uint64_t carry   = (uint64_t) ah * (uint64_t) bh;
  // add high parts of mid1 and mid2 to carry
  // add low parts of mid1 and mid2 to partial, carrying
  //    any carry bits into carry...
}

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

если бы мне пришлось сделать это по-настоящему, я бы написал расширенную процедуру умножения на местном языке ассемблера. то есть, например, умножить на два 64-разрядные целые числа для получения 128-разрядного результата, который хранится в двух 64-разрядных регистрах. Все разумное оборудование обеспечивает эту функциональность в одной собственной инструкции multiply-это не просто доступно из C.

Это один из тех редких случаев, когда наиболее элегантным и простым в программировании решением является использование языка ассемблера. Но это, конечно, не портативный :-(


Я работал с этой проблемой в эти дни, и я должен сказать, что это впечатлило меня количество раз, когда я видел, как люди говорят, что лучший способ узнать, было ли переполнение, — это разделить результат, это совершенно неэффективно и ненужно. Смысл этой функции в том, что она должна быть максимально быстрой.

существует два варианта обнаружения переполнения:

1º-если возможно, создайте переменную результата в два раза большую, чем множители, для пример:

struct INT32struct {INT16 high, low;};
typedef union
{
  struct INT32struct s;
  INT32 ll;
} INT32union;

INT16 mulFunction(INT16 a, INT16 b)
{
  INT32union result.ll = a * b; //32Bits result
  if(result.s.high > 0) 
      Overflow();
  return (result.s.low)
}

вы сразу узнаете, было ли переполнение, и код является максимально быстрым, не записывая его в машинный код. В зависимости от компилятора этот код может быть улучшен в машинный код.

2º-невозможно создать переменную результата в два раза большую, чем переменная множителей:
Затем вы должны играть с условиями if, чтобы определить лучший путь. Продолжая пример:

INT32 mulFunction(INT32 a, INT32 b)
{

  INT32union s_a.ll = abs(a);
  INT32union s_b.ll = abs(b); //32Bits result
  INT32union result;
  if(s_a.s.hi > 0 && s_b.s.hi > 0)
  {
      Overflow();
  }
  else if (s_a.s.hi > 0)
  {
      INT32union res1.ll = s_a.s.hi * s_b.s.lo;
      INT32union res2.ll = s_a.s.lo * s_b.s.lo;
      if (res1.hi == 0)
      {
          result.s.lo = res1.s.lo + res2.s.hi;
          if (result.s.hi == 0)
          {
            result.s.ll = result.s.lo << 16 + res2.s.lo;
            if ((a.s.hi >> 15) ^ (b.s.hi >> 15) == 1)
            {
                result.s.ll = -result.s.ll; 
            }
            return result.s.ll
          }else
          {
             Overflow();
          }
      }else
      {
          Overflow();
      }
  }else if (s_b.s.hi > 0)
{

   //Same code changing a with b

}else 
{
    return (s_a.lo * s_b.lo);
}
}

Я надеюсь, что этот код поможет вам довольно эффективная программа, и я надеюсь, что код понятен, если нет, я поставлю некоторые комментарии.

С наилучшими пожеланиями.


возможно, лучший способ решить эту проблему-иметь функцию, которая умножает два UInt64 и приводит к паре UInt64, верхней части и нижней части результата UInt128. Вот решение, включая функцию, которая отображает результат в hex. Я думаю, вы, возможно, предпочитаете решение C++, но у меня есть рабочее Swift-решение, которое показывает, как управлять проблемой:

func hex128 (_ hi: UInt64, _ lo: UInt64) -> String
{
    var s: String = String(format: "%08X", hi >> 32)
                  + String(format: "%08X", hi & 0xFFFFFFFF)
                  + String(format: "%08X", lo >> 32)
                  + String(format: "%08X", lo & 0xFFFFFFFF)
    return (s)
}

func mul64to128 (_ multiplier: UInt64, _ multiplicand : UInt64)
             -> (result_hi: UInt64, result_lo: UInt64)
{
    let x: UInt64 = multiplier
    let x_lo: UInt64 = (x & 0xffffffff)
    let x_hi: UInt64 = x >> 32

    let y: UInt64 = multiplicand
    let y_lo: UInt64 = (y & 0xffffffff)
    let y_hi: UInt64 = y >> 32

    let mul_lo: UInt64 = (x_lo * y_lo)
    let mul_hi: UInt64 = (x_hi * y_lo) + (mul_lo >> 32)
    let mul_carry: UInt64 = (x_lo * y_hi) + (mul_hi & 0xffffffff)
    let result_hi: UInt64 = (x_hi * y_hi) + (mul_hi >> 32) + (mul_carry >> 32)
    let result_lo: UInt64 = (mul_carry << 32) + (mul_lo & 0xffffffff)

    return (result_hi, result_lo)
}

вот пример, чтобы проверить, что функция работает:

var c: UInt64 = 0
var d: UInt64 = 0

(c, d) = mul64to128(0x1234567890123456, 0x9876543210987654)
// 0AD77D742CE3C72E45FD10D81D28D038 is the result of the above example
print(hex128(c, d))

(c, d) = mul64to128(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF)
// FFFFFFFFFFFFFFFE0000000000000001 is the result of the above example
print(hex128(c, d))

Если вы просто хотите обнаружить переполнение, как насчет преобразования в double, умножения и if

|x /

|x /

в противном случае произведите любую ошибку, которую вы хотите?

Это, кажется, работает:

int64_t safemult(int64_t a, int64_t b) {
  double dx;

  dx = (double)a * (double)b;

  if ( fabs(dx) < (double)9007199254740992 )
    return (int64_t)dx;

  if ( (double)INT64_MAX < fabs(dx) )
    return INT64_MAX;

  return a*b;
}

вот трюк для обнаружения переполнения умножения двух целых чисел без знака.

мы делаем наблюдение, что если мы умножим N-разрядное двоичное число с M-разрядным двоичным числом, произведение не будет иметь более N + M бит.

например, если нас попросят умножить трехразрядное число на двадцать девять, мы знаем, что это не переполнение тридцать два бита.

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

int might_be_mul_oflow(unsigned long a, unsigned long b)
{
  if (!a || !b)
    return 0;

  a = a | (a >> 1) | (a >> 2) | (a >> 4) | (a >> 8) | (a >> 16) | (a >> 32);
  b = b | (b >> 1) | (b >> 2) | (b >> 4) | (b >> 8) | (b >> 16) | (b >> 32);

  for (;;) {
    unsigned long na = a << 1;
    if (na <= a)
      break;
    a = na;
  }

  return (a & b) ? 1 : 0;
}

int main(int argc, char **argv)
{
  unsigned long a, b;
  char *endptr;

  if (argc < 3) {
    printf("supply two unsigned long integers in C formn");
    return EXIT_FAILURE;
  }

  a = strtoul(argv[1], &endptr, 0);

  if (*endptr != 0) {
    printf("%s is garbagen", argv[1]);
    return EXIT_FAILURE;
  }

  b = strtoul(argv[2], &endptr, 0);

  if (*endptr != 0) {
    printf("%s is garbagen", argv[2]);
    return EXIT_FAILURE;
  }

  if (might_be_mul_oflow(a, b))
    printf("might be multiplication overflown");

  {
    unsigned long c = a * b;
    printf("%lu * %lu = %lun", a, b, c);
    if (a != 0 && c / a != b)
      printf("confirmed multiplication overflown");
  }

  return 0;
}

знание тесты: (на 64-битной системе):

$ ./uflow 0x3 0x3FFFFFFFFFFFFFFF
3 * 4611686018427387903 = 13835058055282163709

$ ./uflow 0x7 0x3FFFFFFFFFFFFFFF
might be multiplication overflow
7 * 4611686018427387903 = 13835058055282163705
confirmed multiplication overflow

$ ./uflow 0x4 0x3FFFFFFFFFFFFFFF
might be multiplication overflow
4 * 4611686018427387903 = 18446744073709551612

$ ./uflow 0x5 0x3FFFFFFFFFFFFFFF
might be multiplication overflow
5 * 4611686018427387903 = 4611686018427387899
confirmed multiplication overflow

шаги might_be_mul_oflow почти наверняка медленнее, чем просто выполнение теста разделения, по крайней мере, на основных процессорах, используемых в настольных рабочих станциях, серверах и мобильных устройствах. На чипах без хорошей поддержки подразделения это может быть полезно.


мне кажется, что есть другой способ сделать этот тест на отклонения.

  1. начнем с пары чисел arng и brng, которые инициализировано в 0x7FFF...FFFF и 1.

  2. если a <= arng и b <= brng мы можем заключить, что нет переполнения.

  3. в противном случае, мы shift arng вправо, и shift brng слева, добавив один бит в brng, так что они 0x3FFF...FFFF и 3.

  4. если arng равно нулю, готово; в противном случае повторите на 2.

функция теперь выглядит например:

int might_be_mul_oflow(unsigned long a, unsigned long b)
{
  if (!a || !b)
    return 0;

  {
    unsigned long arng = ULONG_MAX >> 1;
    unsigned long brng = 1;

    while (arng != 0) {
      if (a <= arng && b <= brng)
        return 0;
      arng >>= 1;
      brng <<= 1;
      brng |= 1;
    }

    return 1;
  }
}

БДЗ № 3

Исключения в С++

Цель работы: 1) изучить различные способы обработки исключений; 2) получить практические навыки программирования задач с обработкой исключений.

Теоретические сведения

Механизм обработки исключительных ситуаций в С++ предназначен для отслеживания ошибочных вычислительных ситуаций и безопасной реакции программы на них. Традиционно к исключительным ситуациям относятся, например, деление на ноль, вычисление логарифма отрицательного числа, выход за границы массива и т. д. Однако, допустимо использование этого механизма для отслеживания любой вычислительной ситуации.

Синтаксически обработка исключений реализуется с помощью ключевых слов try, catch и throw:

try { /* блок try, где должен находиться оператор, генерирующий исключения */

}

catch (type1 arg1) { /* блок catch перехвата и обработки исключения типа type1 */

}

catch (type2 arg2) { /* блок catch перехвата и обработки исключения типа type2 */

}

catch (typeN argN) { /* блок catch перехвата и обработки исключения типа typeN */

}

Оператор throw должен выполняться либо внутри блока try, либо в любой функции, которую этот блок вызывает:

throw исключительная_ситуация;

Пример 3.1

#include <iostream.h>

const int n=10;

void main()

{ int i;

int mas[n];

try { for(i=0;i<=n;i++) mas[i]=i;

 if (i>=n) throw i;

            if (i<0) throw “Empty array”;

}

//обработка массива согласно алгоритму

catch (int i) {cerr<<”Out of array”;}

catch (char *err) {cerr<<”Index is negative”;}

}

Приведенный пример легко может обойтись без использования синтаксиса обработки исключений.

В общем случае для каждой ситуации следует подбирать наиболее эффективный и надежный метод обработки ошибок. Исключения очень подходят для случаев, когда нет возможности вернуть значение, указывающее на то, что произошла ошибка. Эта ситуация наиболее вероятна в конструкторах или в функциях, не имеющих возвращаемого значения. Другой случай эффективного использования исключений, когда генерация ошибки и ее обработчик находятся далеко друг от друга.

Пример 3.2Создание стека-массива, использующего исключения для отслеживания его верхней и нижней границы.

#include <iostream>

using namespace std;

const int RANG = 10; // максимальный размер стека

class Stack

{ int stk[RANG];

   int top;              // индекс вершины стека

public:

   class Range1                 // класс исключений для класса Stack,

        {                               // тело класса пусто

        };

   class Range2                 // класс исключений для класса Stack,

        {                               // тело класса пусто

        };

   Stack() { top = -1; }  // конструктор

   void push(int variable)  // функция помещения данных в стек

   { if (top >= RANG — 1)// если стек заполнен, генерировать //исключение

                   throw Range1();

          stk[++top] = variable;// иначе — протолкнуть число в стек

      }

   int pop()            // функция извлечения данных из стека

      { if (top < 0)  // если стек пуст, генерировать исключение

                   throw Range2();           

          return stk[top—];    // иначе — извлечь число из стека

      }

};               // class Stack

int main()

{ Stack s_int;

int INP;

try { for (int i = 0; i < RANG; i++)

            { cout << «Input value»;

                cin >> INP;

                s_int.push(INP);

            }

         s_int.push(INP);     // генерация исключения — стек полон

         for (int i = 0; i < RANG; i++)

             cout << i + 1 << ‘:’ << s_int.pop() << endl;

         cout << «11: » << s_int.pop()    // генерация исключения //- стек пуст

                 << endl;

     }                      // end try

catch (Stack:: Range1)                 // обработчик исключений

{ cout << «Исключение: нарушение границы стека сверху» << endl;   }

catch (Stack:: Range2)         // обработчик исключений

{ cout << «Исключение: нарушение границы стека снизу» << endl;     }

cout << «Завершение работы программы» << endl;

return 0;

}

Контрольные вопросы

1. Какую цель преследует использование в программе обработки исключений?

2. Как оформляется блок обработки исключений?

3. Что такое обработчики исключений?

Варианты заданий

Номер варианта Задание
1, 13 Опишите функцию умножения двух целых, обработайте ошибку переполнения сверху (overflow).
2, 14 Опишите функцию деления двух целых, обработайте ошибку переполнения снизу (underflow).
3, 15 Опишите функцию вычисления логарифма, обработайте ошибку вычисления логарифма 0
4, 16 Переопределите оператор ++ для указателя на массив целых, обработайте ошибку выхода за границы массива.
5, 17 Опишите функцию анализа номера телефона, обработайте ошибку задания номера в неверном формате (допустимый формат — +7(495)555-44-33).
6, 18 Опишите оператор [] для вектора элементов, обработайте ошибку выхода за границы массива.
7, 19 Опишите функцию анализа времени, обработайте ошибку задания даты в неверном формате (допустимый формат – дд-мм-гггг) и ошибку корректности вводимых данных.
8, 20 Опишите функцию умножения двух чисел с плавающей запятой, обработайте ошибку переполнения сверху (overflow).
9, 21 Опишите функцию вычисления логарифма, обработайте ошибку вычисления логарифма отрицательного числа
10, 22 Опишите функцию деления двух чисел с плавающей запятой, обработайте ошибку деления на ноль (zero division).
11, 23 Опишите функцию вычисления квадратного корня, обработайте ошибку вычисления корня из отрицательного числа.
12, 24 Опишите функцию анализа времени, обработайте ошибку задания времени в неверном формате (допустимый формат – чч:мм:сс) и ошибку корректности вводимых данных.

Понравилась статья? Поделить с друзьями:

Читайте также:

  • Опишите основные тактические ошибки при реализации разновидностей систем командной защиты баскетбол
  • Опишите как человеческая деятельность изменила экосистему степи
  • Опишите как правильно исправить допущенную ошибку при заполнении формы оп 14
  • Опишите как можно изменить цвет элемента диаграммы цвет области фона диаграммы
  • Опишите как можно изменить параметры уже существующей гиперссылки например ее адрес

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии