转换为Protobuf / C ++浮点数时,Python浮点数何时会失去精度?

Mar*_*kNS 5 python protocol-buffers

我对最小化从Python序列化的protobuf消息的大小感兴趣。

Protobuf具有浮点数(4个字节)和双精度点(8个字节)。Python的float类型实际上是C的double类型,至少在CPython中如此。

我的问题是:给定一个Python实例float,是否存在一种“快速”的方法来检查将值分配给protobuf float(或实际上是C ++浮点数)是否会失去精度?

Mar*_*ers 5

您可以检查将浮点数转换为十六进制表示形式;符号、指数和分数各有一个单独的部分。假设分数仅使用前 6 位十六进制数字(其余 7 位数字必须为零),并且第 6 位数字是偶数(因此最后一位未设置),您的 64 位双浮点数将适合 32 位单浮点数。指数限制为 -126 到 127 之间的值:

import math
import re

def is_single_precision(
        f,
        _isfinite=math.isfinite,
        _singlepat=re.compile(
            r'-?0x[01]\.[0-9a-f]{5}[02468ace]0{7}p'
            r'(?:\+(?:1[01]\d|12[0-7]|[1-9]\d|\d)|'
            r'-(?:1[01]\d|12[0-6]|[1-9]\d|\d))$').match):
    return not _isfinite(f) or _singlepat(f.hex()) is not None or f == 0.0
Run Code Online (Sandbox Code Playgroud)

float.hex()方法相当快,比通过 struct 或 numpy 进行往返要快;您可以在半秒内创建 100 万个十六进制表示:

>>> timeit.Timer('(1.2345678901e+26).hex()').autorange()
(1000000, 0.47934128501219675)
Run Code Online (Sandbox Code Playgroud)

正则表达式引擎也非常快,通过在上面的函数中优化名称查找,我们可以在大约 1.1 秒内测试 100 万个浮点值:

>>> import random, sys
>>> testvalues = [0.0, float('inf'), float('-inf'), float('nan')] + [random.uniform(sys.float_info.min, sys.float_info.max) for _ in range(2 * 10 ** 6)]
>>> timeit.Timer('is_single_precision(f())', 'from __main__ import is_single_precision, testvalues; f = iter(testvalues).__next__').autorange()
(1000000, 1.1044921400025487)
Run Code Online (Sandbox Code Playgroud)

上面的代码之所以有效,是因为浮点数的二进制 32格式为小数分配了 23 位。指数分配为 8 位(带符号)。正则表达式仅允许设置前 23 位,并且指数位于有符号 8 位数字的范围内。

另请参阅

但这可能不是您想要的!以 1/3 或 1/10 为例。两者都是需要浮点值近似的值,并且都未通过测试:

>>> (1/3).hex()
'0x1.5555555555555p-2'
>>> (1/10).hex()
'0x1.999999999999ap-4'
Run Code Online (Sandbox Code Playgroud)

您可能不得不采取启发式方法;如果您的十六进制值的分数前 6 位全为零,或者指数超出 (-126, 127) 范围,则转换为 double 会导致太多损失。