Как я могу складывать и вычитать 128-битные целые числа в C или C ++, если мой компилятор их не поддерживает?

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

Однако для сжатия мне нужно вычесть эти 128-битные значения, а для распаковки мне нужно добавить эти значения. Максимальный размер целого числа для моего компилятора составляет 64 бита.

У кого-нибудь есть идеи, как сделать это эффективно?


person Billy ONeal    schedule 12.04.2009    source источник


Ответы (7)


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

Я не знаю, что ваш компилятор использует для 64-битных типов, поэтому я буду использовать INT64 и UINT64 для 64-битных целых чисел со знаком и без знака.

class Int128
{
public:
    ...
    Int128 operator+(const Int128 & rhs)
    {
        Int128 sum;
        sum.high = high + rhs.high;
        sum.low = low + rhs.low;
        // check for overflow of low 64 bits, add carry to high
        if (sum.low < low)
            ++sum.high;
        return sum;
    }
    Int128 operator-(const Int128 & rhs)
    {
        Int128 difference;
        difference.high = high - rhs.high;
        difference.low = low - rhs.low;
        // check for underflow of low 64 bits, subtract carry to high
        if (difference.low > low)
            --difference.high;
        return difference;
    }

private:
    INT64  high;
    UINT64 low;
};
person Mark Ransom    schedule 12.04.2009
comment
Вы можете просто использовать stdint.h. - person Tyler; 12.06.2014

Взгляните на GMP.

#include <stdio.h>
#include <gmp.h>

int main (int argc, char** argv) {
    mpz_t x, y, z;
    char *xs, *ys, *zs;
    int i;
    int base[4] = {2, 8, 10, 16};

    /* setting the value of x in base 10 */
    mpz_init_set_str(x, "100000000000000000000000000000000", 10);

    /* setting the value of y in base 16 */
    mpz_init_set_str(y, "FF", 16);

    /* just initalizing the result variable */
    mpz_init(z);

    mpz_sub(z, x, y);

    for (i = 0; i < 4; i++) {
        xs = mpz_get_str(NULL, base[i], x);
        ys = mpz_get_str(NULL, base[i], y);
        zs = mpz_get_str(NULL, base[i], z);

        /* print all three in base 10 */
        printf("x = %s\ny = %s\nz = %s\n\n", xs, ys, zs);

        free(xs);
        free(ys);
        free(zs);
    }

    return 0;
}

На выходе

x = 10011101110001011010110110101000001010110111000010110101100111011111000000100000000000000000000000000000000
y = 11111111
z = 10011101110001011010110110101000001010110111000010110101100111011111000000011111111111111111111111100000001

x = 235613266501267026547370040000000000
y = 377
z = 235613266501267026547370037777777401

x = 100000000000000000000000000000000
y = 255
z = 99999999999999999999999999999745

x = 4ee2d6d415b85acef8100000000
y = ff
z = 4ee2d6d415b85acef80ffffff01
person Chas. Owens    schedule 12.04.2009
comment
Обязательно буду иметь в виду эту библиотеку, если мне нужно больше, чем сложение и вычитание. Спасибо! - person Billy ONeal; 12.04.2009
comment
GMP поставляется с интерфейсом C ++ (#include <gmpxx.h>), который намного проще в использовании ... - person Marc Glisse; 14.01.2013

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

Во-первых, подписанный диапазон 128-битного числа составляет от -2 127 до 2 127 -1, а не от -2 127 до 2 127, как было предусмотрено изначально.

Во-вторых, из-за циклической природы конечной арифметики наибольшая требуемая разность между двумя 128-битными числами составляет от -2 127 до 2 127 -1, что требует наличия хранилища 128 бит, а не 129. Хотя (2 127 -1) - (-2 127) = 2 128 -1, что явно больше чем наше максимальное положительное целое число 2 127 -1, арифметическое переполнение всегда гарантирует, что ближайшее расстояние между любыми двумя n -битными числами всегда будет в диапазоне от 0 до 2 n -1 и, следовательно, неявно от -2 n -1 до 2 n - 1 -1.

Чтобы прояснить ситуацию, давайте сначала рассмотрим, как гипотетический 3-битный процессор будет реализовывать двоичное сложение. В качестве примера рассмотрим следующую таблицу, в которой показан абсолютный беззнаковый диапазон 3-битного целого числа.

0 = 000b
1 = 001b
2 = 010b
3 = 011b
4 = 100b
5 = 101b
6 = 110b
7 = 111b ---> [ Циклический возврат к 000b при переполнении]

Из приведенной выше таблицы легко очевидно, что:

001b (1) + 010b (2) = 011b (3)

Также очевидно, что добавление любого из этих чисел с его числовым дополнением всегда дает 2 n -1:

010b (2) + 101b ([дополнение 2] = 5) = 111b (7) = (2 3 -1)

Из-за циклического переполнения, которое происходит, когда сложение двух n -битных чисел приводит к (n +1) -битному результату, отсюда следует, что добавление любого из этих числа с его числовым дополнением + 1 всегда будут давать 0:

010b (2) + 110b ([дополнение 2] + 1) = 000b (0)

Таким образом, мы можем сказать, что [дополнение к n] + 1 = - n, так что n + [дополнение к n ] + 1 = n + (- n) = 0. Кроме того, если мы теперь знаем, что n + [дополнение к n] + 1 = 0, тогда n + [дополнение n - x] + 1 must = n - (n - x) = x.

Применяя это к нашей исходной 3-битной таблице, получаем:

0 = 000b = [дополнение из 0] + 1 = 0
1 = 001b = [дополнение из 7] + 1 = -7
2 = 010b = [дополнение из 6] + 1 = -6
3 = 011b = [дополнение из 5] + 1 = -5
4 = 100b = [дополнение из 4] + 1 = -4
5 = 101b = [дополнение из 3] + 1 = -3
6 = 110b = [дополнение 2] + 1 = -2
7 = 111b = [дополнение 1] + 1 = -1 ---> [Циклический возврат к 000b при переполнении]

Независимо от того, является ли репрезентативная абстракция положительной, отрицательной или комбинацией того и другого, как подразумевается в арифметике с дополнением до двух со знаком, теперь у нас есть 2 n n - битовые шаблоны, которые могут беспрепятственно обслуживать как положительные 0 до 2 n -1, так и отрицательные 0 до - (2 n ) -1 изменяется по мере необходимости. Фактически, все современные процессоры используют именно такую ​​систему, чтобы реализовать общую схему ALU как для операций сложения, так и для операций вычитания. Когда ЦП встречает команду вычитания i1 - i2, он внутренне выполняет операцию [дополнение + 1] над i2 и впоследствии обрабатывает операнды через схему сложения, чтобы вычислить i1 + [дополнение к i2] + 1. За исключением дополнительного Флаг переполнения с переносом / знаком XOR-gated, сложение как со знаком, так и без знака, а также косвенное вычитание являются неявными.

Если мы применим приведенную выше таблицу к входной последовательности [-2 n -1, 2 n -1 - 1, -2 n -1], как представлено в исходном ответе Вольта, теперь мы можем вычислить следующие n-битные разности:

разница № 1:
(2 n -1 -1) - (-2 n -1 ) =
3 - (-4) = 3 + 4 =
(-1) = 7 = 111b

разница № 2:
(-2 n -1) - (2 n -1 -1 ) =
(-4) - 3 = (-4) + (5) =
(-7) = 1 = 001b

Начиная с нашего seed -2 ​​n -1, теперь мы можем воспроизвести исходную входную последовательность, последовательно применяя каждый из вышеуказанных дифференциалов:

(-2 n -1) + (diff # 1) =
(-4) + 7 = 3 =
2 п -1 -1

(2 n -1 -1) + (diff # 2) =
3 + (-7) = (-4) =
-2 < sup> n -1

Вы, конечно, можете принять более философский подход к этой проблеме и предположить, почему 2 n циклически последовательных числа потребуют более 2 n циклически-последовательных дифференциалов?

Талиадон.

person Taliadon    schedule 29.05.2010
comment
Я должен сказать, что понимаю около 5% этого сообщения. Я могу подтвердить, что использование описанного мной алгоритма позволило мне достичь примерно 20% сжатия идентификаторов CLSID в моем белом списке. - person Billy ONeal; 30.05.2010

Boost 1.53 теперь включает multiprecision:

#include <boost/multiprecision/cpp_int.hpp>
#include <iostream>

// Requires Boost 1.53 or higher
// build: g++ text.cpp

int main()
{
    namespace mp = boost::multiprecision;

    mp::uint128_t a = 4294967296;
    mp::uint256_t b(0);
    mp::uint512_t c(0);

    b = a * a;
    c = b * b;

    std::cout << "c: " << c << "\n";
    return 0;
}

Вывод:

./a.out
c: 340282366920938463463374607431768211456
person Community    schedule 25.04.2013
comment
Самый лучший ответ. Boost заменяет собственные типы, такие как double. Документы Boost прекрасно читаются и содержат много хороших примеров кода. - person Contango; 28.03.2017

Существует много литературы, посвященной математике с большими целыми числами. Вы можете использовать одну из бесплатных библиотек (см. Ссылки) или использовать свою собственную. Хотя я должен вас предупредить, для чего-то более сложного, чем сложение и вычитание (и сдвиги), вам нужно будет использовать нетривиальные алгоритмы.

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

Найдите большое целое число. Кстати, последние версии компиляторов VC ++, IntelC ++ и GCC имеют 128-битные целочисленные типы, хотя я не уверен, что они так легко доступны, как вам хотелось бы (они предназначены для использования с регистрами sse2 / xmms).

person Ash    schedule 12.04.2009
comment
Если я попытаюсь использовать тип __int128 моего компилятора, я получаю следующее сообщение: c: \ ... \ clsidcompressor.h (5): error C4235: используется нестандартное расширение: ключевое слово '__int128' не поддерживается в этой архитектуре - person Billy ONeal; 12.04.2009
comment
Вы, вероятно, изменили цель на что-то, что не имеет вышеупомянутых регистров sse2 / xmms, и поэтому нет смысла использовать __int128. Попробуйте поискать в документации, чтобы узнать, можете ли вы их вообще использовать. В противном случае действительно есть много небольших библиотек, которые сделают эту работу. - person Ash; 12.04.2009
comment
Visual Studio не будет использовать __int128 на x86 или x64. - person Puppy; 29.05.2010
comment
128-битные целые числа совершенно не связаны с SSE, они используют только базовый ALU на x86. - person Marc Glisse; 14.01.2013

TomsFastMath немного похож на GMP (упомянутый выше), но является общественным достоянием и был разработан на основе доведен до предельной скорости (он даже содержит оптимизацию кода сборки для x86, x86-64, ARM, SSE2, PPC32 и AVR32).

person Sophie Alpert    schedule 12.04.2009
comment
GMP находится в рамках LGPL, поэтому вы можете делать все, что угодно разумному человеку: gmplib.org /manual/Copying.html#Copying - person Chas. Owens; 12.04.2009
comment
LGPL по-прежнему имеет некоторые требования, которые могут заставить подумать дважды: вам необходимо предоставить пользователю возможность повторно связать ваше приложение с обновленной / настраиваемой / другой версией библиотеки LGPL. - person Michael Burr; 12.04.2009
comment
@Michael: Думайте о L как о разделяемой библиотеке, а не о чем-либо еще. Если ваше приложение динамически связано, значит, вы уже предоставили необходимую возможность для повторного связывания, потому что можно поместить новую общую библиотеку в путь к библиотеке. - person Adam Hawes; 12.04.2009
comment
Зачем ему нужно обновление, если у него простая цель и она хорошо справляется? - person Marc Glisse; 14.01.2013

Также стоит отметить: если цель состоит в том, чтобы просто улучшить сжатие потока чисел путем его предварительной обработки, то предварительно обработанный поток не должен состоять из точно арифметических различий. Вы можете использовать XOR (^) вместо + и -. Преимущество состоит в том, что 128-битное XOR точно такое же, как два независимых XOR для 64-битных частей, поэтому оно одновременно простое и эффективное.

person Armin Rigo    schedule 08.11.2013