英文:
Why Is uint64 of NaN and MaxFloat64 equal in Golang?
问题
以下是要翻译的内容:
以下条件求值为true
,寻找一个解释为什么是这样的 -
x, y := math.NaN(), math.MaxFloat64
fmt.Println(uint64(x) == uint64(y))
>>> true
具体来说,为什么uint64(math.NaN())
等于9223372036854775808
?
英文:
The following condition evaluates to true
, looking for an explanation as to why -
x, y := math.NaN(), math.MaxFloat64
fmt.Println(uint64(x) == uint64(y))
>>> true
Specifically, why is uint64(math.NaN())
= 9223372036854775808
?
答案1
得分: 3
语言运行时通常不会关注将NaN
值转换为整数时会发生什么。它们通常只会发出相应的硬件指令,然后让“硬件”执行其操作。
那么问题是,在这种情况下硬件会做什么?当然,这取决于你的具体机器/ISA是什么。假设最常见的英特尔x86架构,最可能使用的指令是CVTTSD2SI,但你应该检查程序生成的汇编代码,以确保这确实是情况。以下是关于该指令行为的相关引用:
> 如果转换结果超出有符号四字节整数的范围限制(在64位模式下且REX.W/VEX.W/EVEX.W = 1),则会引发浮点无效异常,如果此异常被屏蔽,则返回不确定的整数值(80000000_00000000H)。
根据这个,发生的情况是处理器引发了无效异常,大多数语言运行时默认屏蔽了该异常,所以你的程序继续执行。因此,你得到了指令指定的值0x8000000000000000
,它相当于十进制的9223372036854775808
,这解释了你观察到的结果。
如果你担心这些转换,你应该在进行转换之前检查有效范围,或者取消屏蔽异常并让程序引发浮点异常。当然,你应该仔细考虑是否值得进行额外的检查,因为这会影响性能。取消屏蔽异常虽然在道义上是“正确”的做法,但也存在问题,因为你不希望你的“数值计算”程序在运行时崩溃。显然,正确的做法取决于你的具体情况/需求。
注意:
这是你的程序在x86上的汇编输出:https://go.godbolt.org/z/YfqKdvY6E 你可以看到第100行和其他行调用了cvttsd2si
。这是一个更简单的程序,编译成更短的汇编代码,输出更容易阅读,显示了在第14行上更突出地使用了cvtsd2si
:https://go.godbolt.org/z/r7Pq5Tqqh
英文:
Language runtimes usually do not pay attention to what happens if you try to convert a NaN
value to an integer. They typically simply issue the corresponding hardware instruction, and let the "hardware" do what it does.
So, the question is what does the hardware do in this case? Of course this depends on what your particular machine/ISA is. Assuming the most common Intel x86 architecture, the corresponding instruction used is most likely CVTTSD2SI, though you should check the assembly generated by your program to make sure this is indeed the case. Here's the relevant quote regarding this instruction's behavior:
> If a converted result exceeds the range limits of signed quadword integer (in 64-bit mode and REX.W/VEX.W/EVEX.W = 1), the floating-point invalid exception is raised, and if this exception is masked, the indefinite integer value (80000000_00000000H) is returned.
Based on this, what's happening is that the processor raises the invalid exception, which is masked by most language run-times by default, so your program simply keeps continuing. Consequently, you get the value 0x8000000000000000
as specified by the instruction, which is equivalent to 9223372036854775808
in decimal, explaining the result you observed.
If you worry about these conversions, you should either check for valid range before doing the conversion, or unmask the exception and let your program get a floating-point exception. Of course, you should carefully consider if it is worth doing the extra check as it will be a performance hit. Unmasking the exception, while morally the "correct" thing to do, is also problematic, as you wouldn't want your "number-crunching" program to crash at run-time. Obviously, the right thing to do depends on exactly what your circumstances/needs are.
Note:
Here's the assembly output for your program on x86: https://go.godbolt.org/z/YfqKdvY6E You can see the call to cvttsd2si
on line 100 and others. Here's a simpler program that compiles to a shorter assembly, whose output is easier to read, and showing the use of cvtsd2si
more prominently on line 14: https://go.godbolt.org/z/r7Pq5Tqqh
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论