首页 > 解决方案 > 在 Cython 中添加双打时精度损失

问题描述

我目前正在尝试使用 Cython 优化 Python 代码。我需要输出完全相同,但我希望精确度有问题。据我了解,Python 具有无限的精度,而 Cython 的“Double”相当于 Python 浮点数。我正在努力使用下面的函数(不允许共享代码 - 它是一个具有类似结构的虚拟函数):

def dummyfunction(c_np.ndarray[double, ndim=1] dummyarray, int a, int b, const c_np.uint8_t[:,:] dummyimg):
    cdef double q = 0.111
    cdef double w = 0.222
    cdef double e = 0.333 
    cdef double[:] dummyview = dummyarray   
    cdef int i, j
    cdef int r, g, b
    for i in range(a):
        for j in range(b):
            r = dummyimg[j][0]
            g = dummyimg[j][1]
            b = dummyimg[j][2]
            dummyarray[i * b + j] = (
                q * r
                + w * g
                + e * b
            )
    dummyarray[:] = dummyview #i'm updating a class attribute in place

我试过打印'q r'、'w g'和'e*b'。这些产品的精度与 Python 中的相同!麻烦在于将这三个值相加。它只留下三位小数。我觉得这是因为在大多数情况下,被总结的 3 个组件之一只有 3 个小数位(例如 35.879999999999995、51.068999999999996、9.348)。不过,Python 似乎将其总结为更高的精度(即 96.29699999999998 与 96.297)。

有什么建议吗?

标签: pythoncythonprecisioncythonize

解决方案


首先,Python 对于整数数学只有任意精度。对于浮点数学,Python float 是 IEEE double=precision(64 位)值,就像 Cython double 一样。

假设您在 x86(或 x86-64)平台上,可能有几个罪魁祸首。x86 架构为浮点数学提供了两种不同的指令集。经典路径使用 x87 指令集,所有计算实际上都是以 80 位(又名“long double”)精度完成的。当一个值(中间值或最终值)存储到内存时,它会被截断为 64 位精度。只要它保留在 FPU 寄存器中,它就会保持完整的 80 位精度。

另一个可用的指令集使用所谓的 SSE(流 SIMD 扩展),它可以同时对多个操作数进行操作。但是,这些计算仅使用类型的“严格”精度(在本例中为 64 位)完成。

我的猜测是 Python 选择了一个代码路径,而 Cython 选择了另一个。同样可能的是,它们都选择了相同的指令集(很可能是 SSE 指令集),但它们以不同的顺序添加中间产品。由于精度有限,求和的顺序会影响结果的准确性。

另外,请注意,在任何一种情况下,计算都将以至少 64 位精度完成。正如您所说,没有“只有 3 位小数”的计算。请记住,与往常一样,计算是以二进制浮点数完成的,而不是十进制数。这些值之间的真正差异可能只有几个低位。


推荐阅读