2

假设我有一些 32 位和 64 位浮点值:

>>> import numpy as np
>>> v32 = np.array([5, 0.1, 2.4, 4.555555555555555, 12345678.92345678635], 
                   dtype=np.float32)
>>> v64 = np.array([5, 0.1, 2.4, 4.555555555555555, 12345678.92345678635], 
                   dtype=np.float64)

我想在不丢失精度的情况下将这些值序列化为文本(或者至少非常接近不丢失精度)。我认为这样做的规范方法是repr

>>> map(repr, v32)
['5.0', '0.1', '2.4000001', '4.5555553', '12345679.0']
>>> map(repr, v64)
['5.0', '0.10000000000000001', '2.3999999999999999', '4.5555555555555554', 
 '12345678.923456786']

但是我想让表示尽可能紧凑以最小化文件大小,所以如果像 2.4 这样的值在没有额外小数的情况下被序列化会很好。是的,我知道这是他们实际的浮点表示,但%g似乎能够解决这个问题:

>>> ('%.7g ' * len(v32)) % tuple(v32)
'5 0.1 2.4 4.555555 1.234568e+07 '
>>> ('%.16g ' * len(v32)) % tuple(v64)
'5 0.1 2.4 4.555555555555555 12345678.92345679 '

我的问题是:%g以这种方式使用是否安全?是否.7.16正确的值,这样精度就不会丢失?

4

3 回答 3

7

Python 2.7 及更高版本已经有了一个智能repr实现,用于将 0.1 打印为0.1. 选择简短输出优先于其他候选,例如0.10000000000000001因为它是该特定数字的最短表示,当读回 Python 时,它往返于完全相同的浮点值。要使用此算法,请将您的 64 位浮点数转换为实际的 Python 浮点数,然后再将它们交给repr

>>> map(repr, map(float, v64))
['5.0', '0.1', '2.4', '4.555555555555555', '12345678.923456786']

令人惊讶的是,结果看起来很自然并且在数字上是正确的。有关 2.7/3.2 的更多信息repr可以在What's New和Mark Dickinson的精彩演讲中找到。

不幸的是,这个技巧不适用于 32 位浮点数,至少在不重新实现 Python 2.7 使用的算法的情况下是这样repr

于 2012-09-19T20:54:23.497 回答
6

To uniquely determine a single-precision (32-bit) floating point number in IEEE-754 format, it can be necessary to use 9 (significant, i.e. not starting with 0, unless the value is 0) decimal digits, and 9 digits are always sufficient.

For double-precision (64-bit) floating point numbers, 17 (significant) decimal digits may be necessary and are always sufficient.

I'm not quite sure how the %g format is specified, by the looks of it, it can let the representation begin with a 0 (0.1), so the safe values for the precision would be .9 and .17.

If you want to minimise the file size, writing the byte representations would produce a much smaller file, so if you can do that, that's the way to go.

于 2012-09-19T20:17:16.777 回答
1

在 2.7 中实现花哨的 repr 的 C 代码主要在 Python/dtoa.c 中(在 Python/pystrtod.c 和 Objects/floatobject.c 中有包装器)。特别是看 _Py_dg_dtoa。应该可以借用这段代码并将其修改为使用浮点数而不是双精度数。然后你可以把它包装在一个扩展模块中,或者只是将它构建为一个 so 并 ctypes 它。

另外,请注意,消息来源说该实现是“受 Guy L. Steele, Jr. 和 Jon L. White 的“如何准确打印浮点数”的启发 [Proc. ACM SIGPLAN '90, pp. 112-126] 。” 因此,您可以通过阅读那篇论文(以及 dtoa.c 注释中记录的任何修改似乎是合适的)自己实现一些不太灵活和更简单的东西。

最后,该代码是对 David Gay 在 AT&T 发布的代码的微小更改,并在许多其他库(NSPR 等)中使用,其中一个可能具有更易于访问的版本。

但在执行任何操作之前,请通过尝试 Python 函数并测量它是否太慢来确保确实存在性能问题。

如果这确实是一个性能关键领域,您可能不想循环遍历列表并首先调用 repr(或您自己的花哨的 C 函数);您可能需要一个函数,它可以一次将浮点数或双精度数的 numpy 数组转换为字符串。(当然,理想情况下,您希望将其构建到 numpy 中。)

最后一个想法:您正在寻找“至少非常接近不丢失精度”。可以想象,仅转换为 double 并使用 repr 就足以满足您的目的,而且显然比其他任何事情都容易得多,因此您至少应该对其进行测试以排除它。

不用说,您还应该测试是否%.9g足够%.17g接近您的目的,因为这是可能工作的下一个最简单的事情。

于 2012-09-19T21:20:54.163 回答