在进行数学运算时,如何获取溢出的部分?
例如,假设32位整数:
unsigned int a = 0Xffffffff;
unsigned int b = 0xffffffff;
unsigned int c = a + b;
在这种情况下,c
是0xfffffffe
,但答案应该是0x1fffffffe
。如何获取溢出的1?
我怎么能对乘法做同样的运算?我能把两个大数字相乘,只得到溢出的部分吗?
bigint库是如何管理的?
@警告不可携带@
- 使用https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html
示例:https://godbolt.org/z/NcyNzR
#include <stdio.h>
int main(void)
{
unsigned int res;
if(__builtin_uadd_overflow(0Xffffffff, 0Xffffffff, &res))
{
printf("Overflowedn");
}
printf("Result: 0x%xn", res);
}
- 使用内联汇编读取进位标志
假设无符号类型的操作数,可以写:
bool cf = a+b<a;
或
bool cf = a>-1-b;
这些作品无论是否存在一个更大类型的作品。
乘法更难;如果没有更大的类型,就无法访问结果的上半部分。如果你有一个,你可以使用它。例如,如果你的操作数是uint32_t
,
uint32_t upper = ((uint64_t)a * b) >> 32;
uint32_t lower = a*b;
否则,你就会陷入一个半大小的类型,并使用长乘法。例如,使用uint64_t a,b;
uint32_t al = a, ah = a>>32;
uint32_t bl = b, bh = b>>32;
然后结果的上半部分是ah*bh
加上将al*bh
、ah*bl
和al*bl
的高位相加的进位。
Bigint库可以避免这种痛苦,只需选择一个最大整数类型宽度的一半的肢体类型。
如何获取溢出的1?
之后以可移植的方式进行操作(不要忘记unsigned int
可能只有16位(:
uint32_t a = 0Xffffffff;
uint32_t b = 0xffffffff;
uint32_t c_low = a + b;
uint32_t c_high;
if(c_low >= a) {
c_high = 0;
} else {
c_high = 1;
}
要事先以可移植的方式(无分支(进行操作:
uint32_t a = 0Xffffffff;
uint32_t b = 0xffffffff;
uint32_t c_low;
uint32_t c_high;
c_high = (a&b) >> 31;
c_low = (a ^ (c_high<<31)) + b;
如何对乘法进行同样的运算?
乘法没有进位,它有一个"上半部分"。明确地如果你将一个有N位的无符号整数与一个有M位的无信号整数相乘,那么结果将有N+M位;如果两个数字的大小相同,那么结果将是原来的两倍。
遗憾的是,C不支持"结果类型大于源类型",因此您需要"预提升"源类型,如:
uint32_t a = 0Xffffffff;
uint32_t b = 0xffffffff;
uint64_t temp = (uint64_t)a * (uint64_t)b;
uint32_t c_low = temp;
uint32_t c_high = temp >> 32;
当然,如果编译器不支持较大的类型,那么您必须将其拆分为较小的部分,例如:
uint32_t a = 0Xffffffff;
uint32_t b = 0xffffffff;
uint32_t a_low = a & 0xFFFF;
uint32_t a_high = a >> 16;
uint32_t b_low = a & 0xFFFF;
uint32_t b_high = b >> 16;
uint32_t temp_0 = a_low * b_low;
uint32_t temp_16a = a_high * b_low;
uint32_t temp_16b = a_low * b_high;
uint32_t temp_32 = a_high * b_high;
uint32_t c_low = temp_0 + (temp16a << 16) + (temp16b << 16);
uint32_t c_high = (temp16a >> 16) + (temp16b >> 16) + temp_32;
bigint库如何管理这一点?
主要;它们使用内联汇编语言,因为大多数CPU支持有效处理较大整数的指令,和/或因为您可以直接访问进位标志。例如对于80x86;CPU具有adc
/sbb
、shld
/shrd
、mul
(具有双宽结果(/div
(具有双宽度分子(;加上可能的扩展(adcx
和adox
(。
在32位80x86汇编语言中,添加的内容可能看起来像:
xor edx,0
add eax,ebx ;c_low = a + b
adc edx,0 ;c_high = carry
乘法可能看起来像:
mul ebx ;edx:eax = a * b