当Python浮标转换成原Buff/C++浮点时,它会在什么时候失去精确性?

2024-04-20 08:57:48 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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

我的问题是:给定一个Python ^ {CD1>}的实例,如果它被赋给了原Buff^ ^ }(或者真的是C++浮点),那么是否有一个“快速”的方法来检查该值是否会丢失精度?在


Tags: 实例消息类型字节序列化精度floatcpython
3条回答

您可以选中convert the float to a hex representation;符号、指数和分数各有一个单独的部分。如果分数只使用前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

float.hex()方法非常快,比通过struct或numpy的往返更快;您可以在半秒内创建一百万个十六进制表示:

^{pr2}$

regex引擎也相当快,通过上面函数中优化的名称查找,我们可以在大约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)

上面的工作是因为浮点的二进制32格式为小数分配了23位。指数分配8位(有符号)。正则表达式只允许设置前23位,指数在有符号8位数字的范围内。在

另请参见

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

>>> (1/3).hex()
'0x1.5555555555555p-2'
>>> (1/10).hex()
'0x1.999999999999ap-4'

您可能需要采用启发式方法;如果十六进制值在分数的前6位数中都是零,或者指数在(-126,127)范围之外,则转换为double会导致过多的损失。在

为了完整起见,下面是注释中提到的“round tripping through struct”方法,它的优点是不需要numpy,但仍能给出准确的结果:

import struct, math
def is_single_precision_struct(x, _s=struct.Struct("f")):
    return math.isnan(x) or _s.unpack(_s.pack(x))[0] == x

is_single_precision_numpy()的时间比较:

  • 单精度(f):[2.5650789737701416,2.5488431453704834,2.551704168319702]
  • 单精度结构(f):[0.3972139358520508,0.39684605598449707,0.39119601249694824]

所以在我的机器上似乎也更快了。在

如果您想要一个简单的解决方案来覆盖几乎所有的角点情况,并且能够正确地检测出超出范围的指数以及较小精度的信息丢失,您可以使用NumPy将您的潜在浮点转换为np.float32对象,然后与原始值进行比较:

import numpy

def is_single_precision_numpy(floatval, _float32=np.float32):
    return _float32(floatval) == floatval

这将自动处理可能存在问题的情况,例如float32次正常范围内的值。例如:

^{pr2}$

使用基于hex的解决方案很难轻松处理这些情况。在

虽然没有@Martijn Pieters基于regex的解决方案快,但速度仍然可观(大约是基于regex的解决方案的一半)。以下是时间安排(其中is_single_precision_re_hex正是Martijn的答案)。在

>>> timeit.Timer('is_single_precision_numpy(f)', 'f = 1.2345678901e+26; from __main__ import is_single_precision_numpy').repeat(3, 10**6)
[2.035495020012604, 2.0115931580075994, 2.013475093001034]
>>> timeit.Timer('is_single_precision_re_hex(f)', 'f = 1.2345678901e+26; from __main__ import is_single_precision_re_hex').repeat(3, 10**6)
[1.1169273109990172, 1.1178153319924604, 1.1184561859990936]

不幸的是,虽然几乎所有的角点情况(次正常值、无穷大值、有符号的零、溢出等)都得到了正确的处理,但是有一种情况下,这个解决方案不适用于:floatval是NaN。在这种情况下,is_single_precision_numpy将返回False。这可能对你的需求有影响也可能不重要。如果这很重要,那么添加一个额外的isnan检查就可以做到了:

import math

def is_single_precision_numpy(floatval, _float32=np.float32, _isnan=math.isnan):
    return _float32(floatval) == floatval or _isnan(floatval)

相关问题 更多 >