简体   繁体   English

如果我的编译器不支持它们,如何在C或C ++中添加和减去128位整数?

[英]How can I add and subtract 128 bit integers in C or C++ if my compiler does not support them?

I'm writing a compressor for a long stream of 128 bit numbers. 我正在为128位数字的长流写一个压缩器。 I would like to store the numbers as differences -- storing only the difference between the numbers rather than the numbers themselves because I can pack the differences in fewer bytes because they are smaller. 我想将数字存储为差异 - 仅存储数字之间的差异而不是数字本身,因为我可以将差异打包在更少的字节中,因为它们更小。

However, for compression then I need to subtract these 128 bit values, and for decompression I need to add these values. 但是,对于压缩,我需要减去这些128位值,对于解压缩,我需要添加这些值。 Maximum integer size for my compiler is 64 bits wide. 我的编译器的最大整数大小是64位宽。

Anyone have any ideas for doing this efficiently? 任何人有任何想法有效地做到这一点?

If all you need is addition and subtraction, and you already have your 128-bit values in binary form, a library might be handy but isn't strictly necessary. 如果您只需要加法和减法,并且您已经拥有二进制形式的128位值,那么库可能很方便,但并不是绝对必要的。 This math is trivial to do yourself. 这个数学很容易做到。

I don't know what your compiler uses for 64-bit types, so I'll use INT64 and UINT64 for signed and unsigned 64-bit integer quantities. 我不知道你的编译器对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;
};

Take a look at GMP . 看看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;
}

The output is 输出是

x = 10011101110001011010110110101000001010110111000010110101100111011111000000100000000000000000000000000000000
y = 11111111
z = 10011101110001011010110110101000001010110111000010110101100111011111000000011111111111111111111111100000001

x = 235613266501267026547370040000000000
y = 377
z = 235613266501267026547370037777777401

x = 100000000000000000000000000000000
y = 255
z = 99999999999999999999999999999745

x = 4ee2d6d415b85acef8100000000
y = ff
z = 4ee2d6d415b85acef80ffffff01

Boost 1.53 now includes multiprecision: 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;
}

Output: 输出:

./a.out
c: 340282366920938463463374607431768211456

Having stumbled across this relatively old post entirely by accident, I thought it pertinent to elaborate on Volte's previous conjecture for the benefit of inexperienced readers. 我完全偶然地偶然发现了这个相对较老的帖子,我认为有必要详细说明Volte之前的猜想,以便为没有经验的读者带来好处。

Firstly, the signed range of a 128-bit number is -2 127 to 2 127 -1 and not -2 127 to 2 127 as originally stipulated. 首先,128位数的有符号范围是-2 127到2 127 -1,而不是最初规定的-2 127到2 127

Secondly, due to the cyclic nature of finite arithmetic the largest required differential between two 128-bit numbers is -2 127 to 2 127 -1, which has a storage prerequisite of 128-bits, not 129. Although (2 127 -1) - (-2 127 ) = 2 128 -1 which is clearly greater than our maximum 2 127 -1 positive integer, arithmetic overflow always ensures that the nearest distance between any two n -bit numbers always falls within the range 0 to 2 n -1 and thus implicitly -2 n -1 to 2 n -1 -1. 其次,由于有限算术的循环性质,两个128位数之间所需的最大差值为-2 127到2 127 -1,其存储先决条件为128位,而不是129.尽管(2 127 -1) - (-2 127 )= 2 128 -1,它明显大于我们的最大2 127 -1正整数,算术溢出总是确保任何两个n位数之间的最近距离总是在0到2 n的范围内 -因此,隐含地为-2 n -1至2 n -1 -1。

In order to clarify, let us first examine how a hypothetical 3-bit processor would implement binary addition. 为了澄清,让我们首先研究一个假设的3位处理器如何实现二进制加法。 As an example, consider the following table which depicts the absolute unsigned range of a 3-bit integer. 例如,请考虑下表,该表描述了3位整数的绝对无符号范围。

0 = 000b 0 = 000b
1 = 001b 1 = 001b
2 = 010b 2 = 010b
3 = 011b 3 = 011b
4 = 100b 4 = 100b
5 = 101b 5 = 101b
6 = 110b 6 = 110b
7 = 111b ---> [Cycles back to 000b on overflow] 7 = 111b ---> [溢出时循环回000b]

From the above table it is readily apparent that: 从上表可以看出:

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

It is also apparent that adding any of these numbers with its numeric complement always yields 2 n -1: 显而易见的是,使用其数字补码添加任何这些数字总是产生2 n -1:

010b(2) + 101b([complement of 2] = 5) = 111b(7) = (2 3 -1) 010b(2)+ 101b([2的补数] = 5)= 111b(7)=(2 3 -1)

Due to the cyclic overflow which occurs when the addition of two n -bit numbers results in an ( n +1)-bit result, it therefore follows that adding any of these numbers with its numeric complement + 1 will always yield 0: 由于在添加两个n位数导致( n + 1)位结果时发生循环溢出,因此,将这些数字与其数字补码+ 1相加将始终产生0:

010b(2) + 110b([complement of 2] + 1) = 000b(0) 010b(2)+ 110b([补码2] + 1)= 000b(0)

Thus we can say that [complement of n ] + 1 = - n , so that n + [complement of n ] + 1 = n + (- n ) = 0. Furthermore, if we now know that n + [complement of n ] + 1 = 0, then n + [complement of n - x ] + 1 must = n - ( n - x ) = x . 因此,我们可以说[ n的补码] + 1 = - n ,因此n + [ n的 ] + 1 = n +( - n )= 0.此外,如果我们现在知道n + [ n的 ] + 1 = 0,然后n + [ n - x ] + 1的补必须= n - ( n - x )= x

Applying this to our original 3-bit table yields: 将其应用于我们原来的3位表会产生:

0 = 000b = [complement of 0] + 1 = 0 0 = 000b = [0的补码] + 1 = 0
1 = 001b = [complement of 7] + 1 = -7 1 = 001b = [7的补数] + 1 = -7
2 = 010b = [complement of 6] + 1 = -6 2 = 010b = [6的补数] + 1 = -6
3 = 011b = [complement of 5] + 1 = -5 3 = 011b = [5的补数] + 1 = -5
4 = 100b = [complement of 4] + 1 = -4 4 = 100b = [4的补数] + 1 = -4
5 = 101b = [complement of 3] + 1 = -3 5 = 101b = [3的补数] + 1 = -3
6 = 110b = [complement of 2] + 1 = -2 6 = 110b = [2的补数] + 1 = -2
7 = 111b = [complement of 1] + 1 = -1 ---> [Cycles back to 000b on overflow] 7 = 111b = [1的补码] + 1 = -1 ---> [溢出时循环回000b]

Whether the representational abstraction is positive, negative or a combination of both as implied with signed twos-complement arithmetic, we now have 2 n n -bit patterns which can seamlessly serve both positive 0 to 2 n -1 and negative 0 to -(2 n )-1 ranges as and when required. 无论代表性抽象是正,负还是两者的组合都表示为带符号二进制补码算法,我们现在有2 n n位模式,它们可以无缝地服务于正0到2 n -1和负0到 - (2) n )-1范围,当需要时。 In point of fact, all modern processors employ just such a system in order to implement common ALU circuitry for both addition and subtraction operations. 事实上,所有现代处理器都采用这样的系统,以便为加法和减法操作实现通用的ALU电路。 When a CPU encounters an i1 - i2 subtraction instruction, it internally performs a [complement + 1] operation on i2 and subsequently processes the operands through the addition circuitry in order to compute i1 + [complement of i2 ] + 1. With the exception of an additional carry/sign XOR-gated overflow flag, both signed and unsigned addition, and by implication subtraction, are each implicit. 当CPU遇到i1 - i2减法指令时,它在内部对i2执行[complement + 1]操作,然后通过加法电路处理操作数,以便计算i1 + [ i2补码] + 1.除了一个附加的进位/符号XOR门控溢出标志,有符号和无符号加法,以及暗示减法,都是隐含的。

If we apply the above table to the input sequence [-2 n -1 , 2 n -1 -1, -2 n -1 ] as presented in Volte's original reply, we are now able to compute the following n-bit differentials: 如果我们将上表应用于Volte原始回复中所示的输入序列[-2 n -1,2 n -1 -1,-2 n -1 ],我们现在可以计算以下n位差分:

diff #1: 差异#1:
(2 n -1 -1) - (-2 n -1 ) = (2 n -1 -1) - (-2 n -1 )=
3 - (-4) = 3 + 4 = 3 - ( - 4)= 3 + 4 =
(-1) = 7 = 111b (-1)= 7 = 111b

diff #2: 差异#2:
(-2 n -1 ) - (2 n -1 -1) = (-2 n -1 ) - (2 n -1 -1)=
(-4) - 3 = (-4) + (5) = (-4)-3 =( - 4)+(5)=
(-7) = 1 = 001b (-7)= 1 = 001b

Starting with our seed -2 n -1 , we are now able to reproduce the original input sequence by applying each of the above differentials sequentially: 从我们的种子-2 n -1开始 ,我们现在能够通过顺序应用上述每个差异来重现原始输入序列:

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

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

You may of course wish to adopt a more philosophical approach to this problem and conjecture as to why 2 n cyclically-sequential numbers would require more than 2 n cyclically-sequential differentials? 你当然希望对这个问题采用一种更哲学的方法,并推测为什么2 n个循环序列数需要超过2 n个循环序列差分?

Taliadon. Taliadon。

There is a lot of literature regarding large integer math. 关于大整数数学有很多文献。 You can use one of the libraries freely available (see links) or you can roll your own. 您可以使用其中一个免费提供的库(请参阅链接),也可以自行编辑。 Although I should warn you, for anything more complicated than addition and subtraction (and shifts), you'll need to use non-trivial algorithms. 虽然我应该警告你,对于比加法和减法(和移位)更复杂的事情,你需要使用非平凡的算法。

To add and subtract, you can create a class/structure that holds two 64-bit integers. 要添加和减去,可以创建一个包含两个64位整数的类/结构。 You can use simple school math to do the addition and subtraction. 您可以使用简单的学校数学来进行加法和减法。 Basically, do what you do with a pencil and paper to add or subtract, with careful consideration to carries/borrows. 基本上,用铅笔和纸做加法或减法,仔细考虑进行/借用。

Search for large integer. 搜索大整数。 Btw recent versions of VC++, IntelC++ and GCC compilers have 128-bit integer types, although I'm not sure they are as easily accessible as you might like (they are intended to be used with sse2/xmms registers). 顺便说一下最新版本的VC ++,IntelC ++和GCC编译器都有128位整数类型,虽然我不确定它们是否像你想要的那样容易访问(它们的目的是与sse2 / xmms寄存器一起使用)。

TomsFastMath有点像GMP(如上所述),但它是公共域,并且从一开始就设计得非常快(它甚至包含x86,x86-64,ARM,SSE2,PPC32和AVR32的汇编代码优化) 。

Also worth noting: if the goal is merely to improve the compression of a stream of numbers by preprocessing it, then the preprocessed stream doesn't have to be made of exactly arithmetic differences. 还值得注意的是:如果目标仅仅是通过预处理来改进数字流的压缩,那么预处理的流不必由精确的算术差异组成。 You can use XOR ( ^ ) instead of + and - . 您可以使用XOR( ^ )代替+- The advantage is that a 128-bit XOR is exactly the same as two independent XORs on the 64-bit parts, so it is both simple and efficient. 优点是128位XOR与64位器件上的两个独立XOR完全相同,因此它既简单又高效。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM