回顾一下问题:
NaN
这总是False
为每次比较返回,所以它保持在列表中的位置:
>>> sorted([float('nan'), 0])
[nan, 0]
>>> sorted([0, float('nan')])
[0, nan]
-0.0
这是 == to 0.0
,但具有不同的 repr、不同的 json 表示形式和略有不同的数值属性。同样的问题是正零和负零将保持与原始列表中相同的顺序:
>>> sorted([0.0, -0.0])
[0.0, -0.0]
>>> sorted([-0.0, 0.0])
[-0.0, 0.0]
其他解决方案?
@khachik 的解决方案对NaN
和的排序行为不一致-inf
>>> key=lambda x: float('-inf') if math.isnan(x) else x
>>> sorted([float('nan'), float('-inf')], key=key)
[nan, -inf]
>>> sorted([float('-inf'), float('nan')], key=key)
[-inf, nan]
解决方案:更复杂的按键功能。
因此,符号和 nans 存在问题。我们可以将它们包含在一个关键函数中:
def stable_float_sort_key(x: float):
return math.copysign(1, x), math.isnan(x), x
这适用于上面的所有示例:
>>> sorted([float('nan'), 0.0], key=stable_float_sort_key)
[0.0, nan]
>>> sorted([0.0, float('nan')], key=stable_float_sort_key)
[0.0, nan]
>>> sorted([float('nan'), float('-inf')], key=stable_float_sort_key)
[-inf, nan]
>>> sorted([float('-inf'), float('nan')], key=stable_float_sort_key)
[-inf, nan]
>>> sorted([0.0, -0.0], key=stable_float_sort_key)
[-0.0, 0.0]
>>> sorted([-0.0, 0.0], key=stable_float_sort_key)
[-0.0, 0.0]
实际上,您可以编写一个假设检验,表明它在所有浮点数中都是一致的:
import json
from hypothesis import given, settings
from hypothesis import strategies as st
@given(nums=st.lists(st.floats()), random=st.randoms())
@settings(max_examples=10000)
def test_stable_json_sorting(nums, random):
shuffled = list(nums)
random.shuffle(shuffled)
l1 = sorted(nums, key=stable_float_sort_key)
l2 = sorted(shuffled, key=stable_float_sort_key)
assert json.dumps(l1) == json.dumps(l2)
然而,它确实有一些奇怪的地方,因为一些 NaN 是负数!例如:
>>> sorted([float('nan'), -0.0, 0.0, float('-nan')], key=stable_float_sort_key)
[-0.0, nan, 0.0, nan]
如果这让您感到困扰,您可以通过切换顺序来解决此问题:
def stable_float_sort_key(x: float):
return math.isnan(x), math.copysign(1, x), x
这首先对负数进行排序,然后是正数,然后是 NaN。
这有什么意义吗?
当然,其他回答者是正确的,从某种意义上说,这一切都没有意义。NaN 的比较是某种概念上的错误。但是,即使在问题没有“意义”的情况下,您也可能需要不变量,例如将由相同代码生成的浮点数集序列化为完全相同的 JSON 表示,尽管哈希随机化(我的用例)。这更像是 python 代码的正式属性,而不是根据 IEEE 标准有“正确答案”的东西。