Python3中IEEE-754浮点数17位十进制表示能否精确还原原值
针对CPython 3.10+使用的IEEE-754 binary64(双精度)标准float类型,你在[0,1]区间的猜想完全成立:所有该区间内的有限浮点数r,都满足float('%.17g' % r) == r,10亿次随机采样没找到反例是必然结果。
理论依据
IEEE-754双精度浮点数的有效二进制位共53位(52位存储尾数+隐含的整数前导1),要保证任意双精度数转十进制字符串再转回二进制浮点数时完全无损,需要的十进制有效位数满足:取最小的整数n使得10^n > 253。计算可得253≈9.007e15,因此n=17——这是IEEE 754标准明确规定的双精度无损往返最小十进制有效位数。
CPython 3.1之后的浮点数格式化采用David Gay的dtoa实现,当指定17位有效数字输出时,生成的十进制字符串满足最短唯一标识要求,转回float时必然匹配原始值。16位有效数字确实不满足要求,存在明确反例:比如可精确表示的双精度值0.10000000000000001,用%.16g格式化会得到字符串"0.1",转回浮点数后和原值不相等。
关联问题解答
该结论是否对所有取值范围的浮点数都成立?
对所有有限双精度浮点数(包括极大值、极小次正规值),只要是用%.17g这类保留17位有效数字的格式化方式,转换回float都等于原值,和数值所处区间无关。g格式会自动在定点表示和科学计数法之间切换,始终保证输出的总有效位数为17,不会因为数值太大或太小损失有效位。
唯一的例外是NaN值:NaN本身不满足x == x的自反相等规则,因此float('%.17g' % float('nan')) == float('nan')会返回False,但转换前后的值都是NaN,语义上是一致的,不属于精度损失。无穷值的转换完全正常,不存在误差。若17位精度下该猜想不成立,提升十进制表示位数后是否可成立?
17位有效数字已经是双精度浮点数无损往返的充分必要最小位数,不存在需要提升位数才能成立的情况。如果使用的是固定小数点后位数的格式化方式(比如%.17f),无论提升到多少位小数点后精度,都不可能覆盖所有取值范围的浮点数:对于量级超过10k的大数,小数点后留再多位也补不够整数部分的有效位;对于量级小于10(-k)的小数,小数点后k位都碰不到有效数字,会被直接截断为0。只有按有效数字计数的格式化逻辑,17位就足够实现全范围无损往返,位数更多不会带来正确性提升,只会增加输出长度。若该猜想不成立,
float('%.17f' % r)的转换结果是否至少具备唯一性?
首先对[0,1]区间的浮点数,%.17f(固定保留小数点后17位)也不是对所有值成立:双精度在接近0的区域精度极高,最小可表示的正浮点数约为5e-324,所有小于5e-18的正浮点数用%.17f格式化时都会输出0.00000000000000000,转回后都是0.0,和原值不相等。
这种情况下转换结果完全不具备唯一性:大量不同的原始浮点数会被格式化为同一个字符串,转回后得到同一个值。如果扩展到全取值范围,问题会更明显:所有大于1e17的浮点数,整数部分长度已经超过17位,%.17f输出时会丢失整数部分的有效位,同样会出现多个不同原始值映射到同一个转换结果的情况。
内容的提问来源于stack exchange,提问作者Xilexio

