问题描述
我目前正在尝试使用 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
我试过打印“qr”、“wg”和“e*b”。这些产品的精度与 Python 中的相同!问题在于将这三个值相加。它只留下三个小数位。我觉得这是因为在大多数情况下,相加的 3 个分量之一最多只有 3 个小数位(例如 35.879999999999995、51.068999999999996、9.348)。不过,Python 似乎将其总结为更高的精度(即 96.29699999999998 与 96.297)。
有什么建议吗?
解决方法
首先,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 位精度完成。正如您所说,没有计算“只有小数点后三位”。请记住,与往常一样,计算是以二进制浮点数完成的,而不是十进制数。值之间的真正差异很可能只有几个低位。