Python float.hex 无损记录:页面都显示成 1,为什么两个浮点值仍然不同

前天 3阅读

排查数值边界时,把变量用六位有效数字写进日志,两个不同值可能都显示成1。随后按日志重建输入,故障又无法复现。面向阅读的显示格式与面向复现的记录格式可以不同;float.hex提供了一种明确保存当前二进制浮点值的文本形式。

本例适用于Python 3.9及以上,使用该版本已有的nextafter和ulp;已在CPython 3.12.14实跑。保存为demo.py并运行python3 demo.py。程序只操作固定数字,不涉及外部文件或金融计算规则。

Python float.hex 无损记录:页面都显示成 1,为什么两个浮点值仍然不同

AI模型生成概念图:同一形状通过编码框架后保留细小缺口,用来比喻精确记录数值细节;它不是位布局或实际测量图。

选择刚好能被显示格式抹平的输入

nextafter从1.0朝2.0取相邻浮点值。它足够接近1,六位有效数字会把它显示成1,却仍是不同的浮点值。程序同时保存hex文本与短显示文本,再分别还原,直接检查两条路径是否保留原值。

完整可运行程序

import math
import struct

value = math.nextafter(1.0, 2.0)
token = value.hex()
short = format(value, ".6g")
assert float.fromhex(token) == value
assert float(short) != value
print("value:", repr(value))
print("hex token:", token)
print("six significant digits:", short)
print("short text round trip:", float(short) == value)

samples = [value, math.ulp(0.0), -0.0, 1e300]
for number in samples:
    restored = float.fromhex(number.hex())
    assert struct.pack(">d", restored) == struct.pack(">d", number)
print("finite binary round trips:", len(samples))
print("negative zero token:", (-0.0).hex())
assert math.copysign(1.0, float.fromhex((-0.0).hex())) == -1.0

hex_fraction = float.fromhex("0.1")
assert hex_fraction == 1 / 16
print("fromhex 0.1:", hex_fraction)
nan = float.fromhex(float("nan").hex())
assert math.isnan(nan) and nan != nan
print("nan equality:", nan == nan)

本次实际输出

value: 1.0000000000000002
hex token: 0x1.0000000000001p+0
six significant digits: 1
short text round trip: False
finite binary round trips: 4
negative zero token: -0x0.0p+0
fromhex 0.1: 0.0625
nan equality: False

p后的数字是二的指数

输出里的0x1.0000000000001p+0是十六进制有效数乘以二的零次幂。它末尾那一个十六进制位保存了短显示中丢掉的差异。fromhex识别的是这种十六进制数值语法,不能把普通十进制文本随便交给它。

后面的fromhex 0.1得到0.0625正是反例:省略0x并没有把入口变成十进制解析器,十六进制小数位上的1表示十六分之一。接口字段若采用这种格式,必须把编码方式写进协议,不能只叫value_text让接收方猜。

负零需要比普通相等更多一步

四个样本包含相邻值、最小正次正规数、负零和很大的有限值。程序把往返前后的数值按同一双精度格式编码为字节,再比较;这比仅写restored == number更能发现负零符号是否丢失。

-0.0与0.0在普通数值比较中相等,因此样本还打印-0x0.0p+0,并用copysign确认恢复后仍带负号。这里保存的是当前float的表示,无法找回它最初由文本或测量转换时已丢失的信息,也不会提高后续计算精度。

NaN不适合沿用普通相等断言

最后一行nan equality为False,不表示这次解析一定失败。NaN按自身的比较规则就不等于自己,应通过isnan判断。hex输出的nan文本也不承担保留所有底层NaN载荷位的任务;若系统要求逐位保存这些特殊表示,应另外使用明确的二进制格式。

Python对正常有限float的repr通常已经能做到往返,不应把所有十进制文本都说成必然失真。这里失败的是人为限制为六位有效数字的显示。hex的价值是把二进制数值结构写得明确,便于边界复现和与支持相同语法的工具核对。

参考资料

资料核验日期:2026年10月2日。代码输出来自本文固定输入的本地实跑。

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。