如何在numpy中向零舍入负数时消除额外的减号?

21

我有一个简单的关于numpy中的fixfloor函数的问题。 在将大于-1且向零舍入的负数四舍五入时,numpy正确地将它们舍入为零,但保留了负号。这个负号会干扰我的自定义unique_rows函数,因为它使用ascontiguousarray来比较数组元素,并且这个符号会扰乱唯一性。在这方面,round和fix表现相同。

>>> np.fix(-1e-6)
Out[1]: array(-0.0)
>>> np.round(-1e-6)
Out[2]: -0.0

有没有什么方法可以去掉符号?我考虑过使用np.sign函数,但它会增加额外的计算成本。


4
尝试将0.0加到结果中。 - Mark Ransom
1
这个之前的问题是否相关?https://dev59.com/A3A75IYBdhLWcg3wRWyc - Weather Vane
1
或者在输入中添加0:np.round(1e-6) + 0. 这样几乎不会增加计算开销,并且可以解决问题。 - bfree67
2个回答

17

你在-0.+0.之间遇到的问题是浮点数应该如何行为的规范(IEEE754)的一部分。在某些情况下,需要区分这两个值。例如,请参阅链接到around文档中的文档。

值得注意的是,两个零应该被视为相等,因此

np.array(-0.)==np.array(+0.) 
# True

换句话说,我认为问题更可能出在你的唯一性比较上面。例如:

a = np.array([-1., -0., 0., 1.])
np.unique(a)
#  array([-1., -0.,  1.])

如果您想保留数字的浮点数形式,但所有的零都相同,可以使用:

x = np.linspace(-2, 2, 6)
#  array([-2. , -1.2, -0.4,  0.4,  1.2,  2. ])
y = x.round()
#  array([-2., -1., -0.,  0.,  1.,  2.])
y[y==0.] = 0.
#  array([-2., -1.,  0.,  0.,  1.,  2.])

# or  
y += 0.
#  array([-2., -1.,  0.,  0.,  1.,  2.])    

请注意,您需要进行这样一点额外的工作,因为您试图避免浮点数规范。

还要注意,这并不是由于四舍五入误差导致的。例如,

np.fix(np.array(-.4)).tostring().encode('hex')
# '0000000000000080'
np.fix(np.array(-0.)).tostring().encode('hex')
# '0000000000000080'

也就是说,最终的数字完全相同,但是

np.fix(np.array(0.)).tostring().encode('hex')
# '0000000000000000'

不同。这就是你的方法无法工作的原因,因为它在比较数字的二进制表示时有所不同,这两个零的二进制表示是不同的。因此,我认为问题更多地是比较方法而不是比较浮点数唯一性的一般想法。

各种方法的快速timeit测试:

data0 = np.fix(4*np.random.rand(1000000,)-2)
#   [ 1. -0.  1. -0. -0.  1.  1.  0. -0. -0. .... ]

N = 100
data = np.array(data0)
print timeit.timeit("data += 0.", setup="from __main__ import np, data", number=N)
#  0.171831846237
data = np.array(data0)
print timeit.timeit("data[data==0.] = 0.", setup="from __main__ import np, data", number=N)
#  0.83500289917
data = np.array(data0)
print timeit.timeit("data.astype(np.int).astype(np.float)", setup="from __main__ import np, data", number=N)
#  0.843791007996

我同意@senderle的观点,如果你想做简单且精确的比较,并且可以使用整数,则通常会更容易。但是,如果您需要独特的浮点数,则也应该能够做到,尽管需要更加小心谨慎。 浮点数的主要问题在于可以进行计算时引入的小差异,在普通的print中不会出现,但这并不是一个巨大的障碍,尤其是在对一定范围内的浮点数进行round, fix, rint之后。


我认为如果必须使用浮点数,这是一个不错的方法。(我想知道它与Mark Ransom添加0.0的想法相比如何。)此外,我认为正零和负零之所以不同,是因为问题中链接到的唯一性测试将数据转换为np.void - senderle
感谢@Mark Ransom和@tom10的帮助。在fixround命令的答案后添加0.0可以消除多余的负号,这个原因在上面已经详细解释过了。 在解决了这个问题之后,我能够编写一个用于在numpy数组中查找唯一行的Python函数,并且还可以选择接受精度(小数位数)。这个函数可以在这里找到。 - Arash_D_B

7
我认为根本问题在于你正在使用浮点数的集合操作——这是一般情况下要避免的,除非你有非常好的理由和对浮点数的深刻理解。
遵循这个规则的明显原因是,即使两个浮点数之间的差异非常小,也会被注册为绝对差异,因此数值误差可能导致集合操作产生意外的结果。现在,在你的用例中,可能最初似乎通过先四舍五入而避免了这个问题,从而限制了可能值的范围。但事实证明,仍然可能出现意外的结果,正如这个边角案例所示。浮点数很难推理。
我认为正确的修复方法是先四舍五入,然后使用astype转换为int。
>>> a
array([-0.5,  2. ,  0.2, -3. , -0.2])
>>> numpy.fix(a)
array([-0.,  2.,  0., -3., -0.])
>>> numpy.fix(a).astype(int)    # could also use 'i8', etc...
array([ 0,  2,  0, -3,  0])

由于您已经四舍五入,因此不会丢失任何信息,并且后续的类似集合的操作将更加稳定和可预测。这是那些最好使用正确抽象的情况之一!

如果您需要浮点数,可以随时转换回去。唯一的问题是这会创建另一个副本; 但大多数情况下这并不是真正的问题。 numpy 足够快,复制的开销非常小!

我要补充的是,如果您的情况确实需要使用浮点数,则tom10的答案是很好的选择。但是我认为,既需要浮点数又需要类似集合的操作的情况非常少。


我同意你的解决方案(所以+1),但我认为原因是IEEE754标准指定0.-0.不同(尽管它们应该比较相等)。 - tom10
@tom10,楼主似乎已经意识到了这一点,你不觉得吗?但是,这比你所建议的更加复杂,因为我们正在谈论特定的“舍入”。我不知道标准在定义的四种舍入规则中对有符号零的规定是什么。而且,如果numpy想要的话,它可能会忽略这些规则并仅将其舍入为正零!我认为,无论使用哪个特定的标准,这些问题都会很困难。 - senderle
我会删除我的评论并写下自己的答案。你在这里明确指出问题是“数值误差”,而我想说这不是问题所在。但我会在几分钟内删除这两个评论,以免混淆视听。 - tom10
@tom10,我不觉得需要删除你的评论。我猜我的回答没有表达清楚——但是我并没有说问题是数值误差。我说的是问题在于在集合操作中使用浮点数——就这样。我会重新表述以澄清。 - senderle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接