将 2**32 平方为 128 位类型失败

Squaring 2**32 into a 128-bit type fails

提问人:rogerl 提问时间:3/15/2023 最后编辑:rogerl 更新时间:3/15/2023 访问量:86

问:

我希望以下 C 代码会为第一个(确实如此)和第二个(但是,它会产生 )。为什么?(这是在 Intel Core i7 上的 MacOS 上。8, 16printf1, 00, 0

typedef unsigned long long ll;
typedef unsigned __int128 bigint;

void main() {
  ll q;
  bigint s;
  q = 4294967296ULL; // 2**32
  s = q*q;
  printf("%d, %d\n", sizeof(ll), sizeof(unsigned __int128));
  printf("%lld, %lld\n", s / q, s % q);
}

编辑:将代码修改为以下内容后,结果是相同的(请注意:在原始代码中,我没有尝试打印任何 128 位值)。

void main() {
  ll q;
  bigint s;
  q = 4294967296ULL; // 2**32
  s = q*q;
  printf("%zu, %zu\n", sizeof(ll), sizeof(unsigned __int128));
  printf("%llu, %llu\n", (ll)(s / q), (ll)(s % q)); 
}
c int128

评论

2赞 Ted Lyngmo 3/15/2023
您使用了错误的格式字符串,而且,我认为不支持 sprintfsizeofprintf__int128
1赞 Daniel Walker 3/15/2023
如果显式转换 and to 会发生什么?s / qs % qlong long
2赞 Rogue 3/15/2023
可能值得注意的是,您的类型是,但您在格式字符串中指定了有符号类型。unsignedprintf
4赞 Ted Lyngmo 3/15/2023
@DanielWalker我想说这是:)的正确转换说明符%zusizeof
3赞 Ted Lyngmo 3/15/2023
@DanielWalker 在实际操作中,我不得不通过大量的代码来修复它,因为人们在“过去”做出了这样的假设。

答:

2赞 Eric Postpischil 3/15/2023 #1

In 是一个 64 位 ,乘法是用 64 位算术执行的。由于值为 232,因此数学乘积 2 64 不适合64 位 .它包装模 264,因此计算结果为 0。s = q*q;qunsigned long longqunsigned long long

因此,被赋值 0,这就是打印的内容(除了注释中指出的转换说明符的问题)。sprintf

您可以通过将至少一个操作数转换为所需类型来请求 128 位算术:

s = (bigint) q * q;
2赞 Ted Lyngmo 3/15/2023 #2

在乘法中,使用 s 而不是 s 完成,因此结果会溢出。要么将操作数强制转换为“从头开始”,要么从头开始。s = q*q;unsigned long longunsigned __int128unsigned __int128qunsigned __int128

您还对 使用了错误的转换说明符。用于 .除此之外,除非 gcc 具有用于打印 128 位类型的扩展,否则您需要找到另一种打印方式。printf%zusizeofprintf

例:

#include <inttypes.h>
#include <limits.h>
#include <stdio.h>

typedef unsigned __int128 ubigint;

void print(ubigint x) {
    // print top 64 bits first and the lower 64 bits last:
    printf("%016" PRIx64 "%016" PRIx64, (uint64_t)(x >> 64), (uint64_t)x);
}

int main() {
    ubigint q;         // correct type
    ubigint s;

    q = 4294967296ULL;
    s = q * q;         // no overflow anymore

    printf("%zu, %zu\n", sizeof(long long), sizeof(ubigint));

    print(s);
    putchar('\n');
    print(s / q);
    putchar('\n');
    print(s % q);
    putchar('\n');
}

输出

8, 16
00000000000000010000000000000000
00000000000000000000000100000000
00000000000000000000000000000000
2赞 Bodo 3/15/2023 #3

计算的类型会导致溢出并截断为 的类型。然后将截断的结果分配给具有更大类型的结果。s = q*q;qqs

强制转换为较大的类型以强制在较大的类型中进行计算。s

s = (bigint)q * q;

或者将 的值赋给较大类型的变量,并将其用于计算。q

s = q;
s = s * s;
1赞 0___________ 3/15/2023 #4

您需要投射:

typedef unsigned long long ll;
typedef unsigned __int128 bigint;

int main(void)
{
  ll q;
  bigint s,d;
  q = 4294967296ULL; // 2**32
  s = (bigint)q*q;
  d = q*q;
  printf("%lld, %lld\n", (ll)(s / q), (ll)(s % q));
  printf("%lld, %lld\n", (ll)(d / q), (ll)(d % q));
}   

https://godbolt.org/z/6EKc7WsxW