关于使用double表示超大整数的困惑及相关技术疑问
256位整数转double的精度问题及疑问解答
测试场景与现象
用__uint128_t[2]存储256位整数时,测试将大整数转double出现明显精度偏差:
测试代码1
#include <stdio.h> int main() { __uint128_t a = 0xffffffffffffffff; a<<=64; a+=0xffffffffffffffff; double b = a; printf("Output - %lf\n", b); return 0; }
输出结果:Output - 340282366920938463463374607431768211456.000000,比正确值340282366920938463463374607431768211455大1。
测试代码2
#include <stdio.h> int main() { __uint128_t a = 0xffffffffffffffff; a<<=64; double b = a; printf("Output - %lf\n", b); return 0; }
输出结果:Output - 340282366920938463463374607431768211456.000000,与正确值340282366920938463444927863358058659840偏差极大。
疑问解答
1. 上述现象的原因是什么?为何第一次结果仅偏差1?
根源在于double的存储逻辑:它是64位浮点数,采用1位符号位+11位指数位+52位尾数位的结构存储,尾数位还隐含了1位最高有效位,所以实际只能精确表示那些可以拆成不超过53个2^k整数倍相加的数——简单说就是2^53以内的所有整数都能精确存储,超过这个值后,只有部分数(比如2的整数次幂)能被精确表示。
- 第一次测试的数值是
2^128 - 1,这个数比2^128小1。double存储2^128时,指数位对应128,尾数位全0(因为是2的整数次幂)。而2^128 - 1无法用53位尾数精确表示,double会自动选择最接近的可表示值,也就是2^128,所以偏差只有1。 - 第二次测试的数值是
2^64 * (2^64 - 1) = 2^128 - 2^64,这个数和2^128的差值是2^64。但double在2^128这个量级下,相邻两个可表示值的间隔是2^(128 - 52) = 2^76,远大于2^64,所以2^128 - 2^64和2^128之间没有其他可表示的double值,只能被近似成2^128,导致偏差极大。
2. double可精确表示的最大正整数是多少?
网上的两种说法对应不同概念,别搞混:
2^53(精确值为9007199254740992):这是能连续精确表示的最大整数,意思是小于等于这个值的所有整数,都能被double精确存储。超过这个值后,就不是所有整数都能精确表示了,只有部分符合尾数规则的数(比如2的整数次幂、2^53 + 2这类偶数)可以。1.8×10^308:这是double能表示的最大正数值(实际接近2^1024),但这个数是浮点数,只能精确表示2的整数次幂这类特殊结构的数,绝大多数整数在这个量级都无法精确存储。
如果问“能精确表示的整数有没有最大值”,其实没有——比如2^1023、2^1023 + 2^52这类数都能精确表示,但它们不是连续的。但如果问“能连续精确表示的最大整数”,那就是2^53。
3. 对两个48位整数做除法并保留小数点后一位,能否提升可表示的最大整数范围?
完全可以,因为你的需求不是精确存储原48位整数,而是处理除法后的一位小数结果:
- 首先,48位整数的最大值是
2^48 - 1,远小于2^53,所以两个48位整数都能被double精确存储,不会丢失精度。 - 它们的除法结果,只要最终保留一位小数,这个值就能被
double精确表示:double的尾数有53位,而十进制一位小数对应二进制最多4位,53位的精度完全能覆盖这个需求。 - 实际处理时,直接把两个48位整数转成
double做除法,再用round(result * 10) / 10这类方式保留一位小数,就能得到精确的结果,完全不用担心原整数的大小问题。
内容的提问来源于stack exchange,提问作者Knm
相关产品推荐
相关产品推荐

