NaN的位模式是否真的依赖于硬件? [英] Are the bit patterns of NaNs really hardware-dependent?

查看:204
本文介绍了NaN的位模式是否真的依赖于硬件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在阅读Java语言规范中的浮点型NaN值(我很无聊)。 32位 float 具有以下格式:

  seee eeee emmm mmmm mmmm mmmm mmmm mmmm 

s 是符号位, e 是指数位, m 是尾数位。 NaN值被编码为全1的指数,并且尾数位不全是0(这将是+/-无穷大)。这意味着有许多不同的可能的NaN值(具有不同的 s m 位值)。



在此, JLS§4.2.3说:


IEEE 754允许每个不同的NaN值的单浮点和双浮点格式。当生成新的NaN时,每个硬件体系结构都会为NaN返回一个特定的位模式,程序员也可以使用不同的位模式来创建NaN,以编码例如回顾性​​诊断信息。

JLS中的文本似乎意味着例如 0.0 / 0.0 的结果具有与硬件相关的位模式,并根据该表达式是否作为编译时间常数计算得出,它所依赖的硬件可能是编译Java程序的硬件,也可能是程序运行的硬件。这一切似乎非常flaky,如果真的。

我跑了以下测试:

 的System.out.println(Integer.toHexString(Float.floatToRawIntBits(0.0F / 0.0F))); 
System.out.println(Integer.toHexString(Float.floatToRawIntBits(Float.NaN)));
System.out.println(Long.toHexString(Double.doubleToRawLongBits(0.0d / 0.0d)));
System.out.println(Long.toHexString(Double.doubleToRawLongBits(Double.NaN)));

我的机器上的输出是:

  7fc00000 
7fc00000
7ff8000000000000
7ff8000000000000

输出没有显示出预期的结果。指数位全部为1.尾数的高位也是1,这对NaN来说显然表示安静的NaN,而不是信号NaN( https://en.wikipedia.org/wiki/NaN#Floating_point )。符号位和尾数位的其余部分都是0.输出还显示在我的机器上生成的NaN和Float和Double类中的常量NaN之间没有区别。



我的问题是,无论编译器或虚拟机的CPU如何,是以Java保证的输出,还是真的不可预测? JLS对此很神秘。

如果保证输出 0.0 / 0.0 ,有没有算术方法生产有其他(可能是硬件依赖?)位模式的NaN? (我知道 intBitsToFloat / longBitsToDouble 可以编码其他NaN,但是我想知道是否可以从其他值正常的算术。)






后续观察点:我注意到 Float.NaN Double.NaN 指定它们的确切位模式,但是在源代码中( Float Double )他们由 0.0 / 0.0 生成。如果这种划分的结果真的取决于编译器的硬件,那么在规范或实现中似乎就有一个缺陷。

解决方案

这就是 JVM 7规范的§2.3.2必须说明一下:


值集合恰好是可以使用IEEE 754标准中定义的双浮点格式
表示的值,除了
只有一个NaN值(IEEE 754指定了2 53 -2不同的NaN值)。

§2.8.1

< blockquote>

Java虚拟机没有信号NaN值。

所以技术上只有一个NaN。但§4.2.3of JLS的也说(就在你的引用之后):


大多数情况下,Java SE平台将NaN值一个给定的类型就好像被折叠成一个单一的规范值一样,因此这个规范通常指向一个任意的NaN,就像是一个规范的值一样。然而,1.3版本的Java SE平台引入了方法,使程序员能够区分NaN值:Float.floatToRawIntBits和Double.doubleToRawLongBits方法。感兴趣的读者可以参考Float和Double类的规范以获取更多信息。


CandiedOrange 建议:它依赖于底层处理器,但是Java对待它们都是一样的。



但它会变得更好:显然,将NaN值完全转换为不同的NaN是完全可能的,如 Double.longBitsToDouble()
$ b


请注意,此方法可能无法返回与long参数完全相同的位模式的双倍NaN。 IEEE 754区分了两种NaN,安静的NaN和信令NaN。这两种NaN之间的区别在Java中通常是不可见的。信令NaN上的算术运算把它们变成了安静的NaN,它们具有不同的,但通常相似的位模式。但是,在一些处理器上,仅仅复制一个信令NaN也执行那个转换。特别是,拷贝一个信令NaN将其返回给调用方法可以执行这个转换。所以longBitsToDouble可能无法返回具有信号NaN位模式的双精度型。因此,对于一些long值,doubleToRawLongBits(longBitsToDouble(start))可能不等于start。此外,哪些特定的位模式表示信令NaN是平台相关的;虽然所有的NaN位模式,安静或信号,必须在上面确定的NaN范围。

作为参考,有一个表依赖于硬件的NaN 此处。总结:

$ p $ - x86:
quiet:Sign = 0 Exp = 0x7ff Frac = 0x80000
信号:Sign = 0 Exp = 0x7ff Frac = 0x40000
- PA-RISC:
quiet:Sign = 0 Exp = 0x7ff Frac = 0x40000
信号:Sign = 0 Exp = 0x7ff Frac = 0x80000
- 电源:
quiet:Sign = 0 Exp = 0x7ff Frac = 0x80000
信号:Sign = 0 Exp = 0x7ff Frac = 0x5555555500055555
- Alpha:
quiet:Sign = 0 Exp = 0 Frac = 0xfff8000000000000
signalling:Sign = 1 Exp = 0x2aa Frac = 0x7ff5555500055555



<所以,要验证这一点,你真的需要这些处理器之一,去尝试一下。也欢迎任何关于如何解释Power和Alpha体系结构较长的值的见解。

I was reading about floating-point NaN values in the Java Language Specification (I'm boring). A 32-bit float has this bit format:

seee eeee emmm mmmm mmmm mmmm mmmm mmmm

s is the sign bit, e are the exponent bits, and m are the mantissa bits. A NaN value is encoded as an exponent of all 1s, and the mantissa bits are not all 0 (which would be +/- infinity). This means that there are lots of different possible NaN values (having different s and m bit values).

On this, JLS §4.2.3 says:

IEEE 754 allows multiple distinct NaN values for each of its single and double floating-point formats. While each hardware architecture returns a particular bit pattern for NaN when a new NaN is generated, a programmer can also create NaNs with different bit patterns to encode, for example, retrospective diagnostic information.

The text in the JLS seems to imply that the result of, for example, 0.0/0.0, has a hardware-dependent bit pattern, and depending on whether that expression was computed as a compile time constant, the hardware it is dependent on might be the hardware the Java program was compiled on or the hardware the program was run on. This all seems very flaky if true.

I ran the following test:

System.out.println(Integer.toHexString(Float.floatToRawIntBits(0.0f/0.0f)));
System.out.println(Integer.toHexString(Float.floatToRawIntBits(Float.NaN)));
System.out.println(Long.toHexString(Double.doubleToRawLongBits(0.0d/0.0d)));
System.out.println(Long.toHexString(Double.doubleToRawLongBits(Double.NaN)));

The output on my machine is:

7fc00000
7fc00000
7ff8000000000000
7ff8000000000000

The output shows nothing out of the expected. The exponent bits are all 1. The upper bit of the mantissa is also 1, which for NaNs apparently indicates a "quiet NaN" as opposed to a "signalling NaN" (https://en.wikipedia.org/wiki/NaN#Floating_point). The sign bit and the rest of the mantissa bits are 0. The output also shows that there was no difference between the NaNs generated on my machine and the constant NaNs from the Float and Double classes.

My question is, is that output guaranteed in Java, regardless of the CPU of the compiler or VM, or is it all genuinely unpredictable? The JLS is mysterious about this.

If that output is guaranteed for 0.0/0.0, are there any arithmetic ways of producing NaNs that do have other (possibly hardware-dependent?) bit patterns? (I know intBitsToFloat/longBitsToDouble can encode other NaNs, but I'd like to know if other values can occur from normal arithmetic.)


A followup point: I've noticed that Float.NaN and Double.NaN specify their exact bit pattern, but in the source (Float, Double) they are generated by 0.0/0.0. If the result of that division is really dependent on the hardware of the compiler, it seems like there is a flaw there in either the spec or the implementation.

解决方案

This is what §2.3.2 of the JVM 7 spec has to say about it:

The elements of the double value set are exactly the values that can be represented using the double floating-point format defined in the IEEE 754 standard, except that there is only one NaN value (IEEE 754 specifies 253-2 distinct NaN values).

and §2.8.1:

The Java Virtual Machine has no signaling NaN value.

So technically there is only one NaN. But §4.2.3 of the JLS also says (right after your quote):

For the most part, the Java SE platform treats NaN values of a given type as though collapsed into a single canonical value, and hence this specification normally refers to an arbitrary NaN as though to a canonical value.

However, version 1.3 of the Java SE platform introduced methods enabling the programmer to distinguish between NaN values: the Float.floatToRawIntBits and Double.doubleToRawLongBits methods. The interested reader is referred to the specifications for the Float and Double classes for more information.

Which I take to mean exactly what you and CandiedOrange propose: It is dependent on the underlying processor, but Java treats them all the same.

But it gets better: Apparently, it is entirely possible that your NaN values are silently converted to different NaNs, as described in Double.longBitsToDouble():

Note that this method may not be able to return a double NaN with exactly same bit pattern as the long argument. IEEE 754 distinguishes between two kinds of NaNs, quiet NaNs and signaling NaNs. The differences between the two kinds of NaN are generally not visible in Java. Arithmetic operations on signaling NaNs turn them into quiet NaNs with a different, but often similar, bit pattern. However, on some processors merely copying a signaling NaN also performs that conversion. In particular, copying a signaling NaN to return it to the calling method may perform this conversion. So longBitsToDouble may not be able to return a double with a signaling NaN bit pattern. Consequently, for some long values, doubleToRawLongBits(longBitsToDouble(start)) may not equal start. Moreover, which particular bit patterns represent signaling NaNs is platform dependent; although all NaN bit patterns, quiet or signaling, must be in the NaN range identified above.

For reference, there is a table of the hardware-dependant NaNs here. In summary:

- x86:     
   quiet:      Sign=0  Exp=0x7ff  Frac=0x80000
   signalling: Sign=0  Exp=0x7ff  Frac=0x40000
- PA-RISC:               
   quiet:      Sign=0  Exp=0x7ff  Frac=0x40000
   signalling: Sign=0  Exp=0x7ff  Frac=0x80000
- Power:
   quiet:      Sign=0  Exp=0x7ff  Frac=0x80000
   signalling: Sign=0  Exp=0x7ff  Frac=0x5555555500055555
- Alpha:
   quiet:      Sign=0  Exp=0      Frac=0xfff8000000000000
   signalling: Sign=1  Exp=0x2aa  Frac=0x7ff5555500055555

So, to verify this you would really need one of these processors and go try it out. Also any insights on how to interpret the longer values for the Power and Alpha architectures are welcome.

这篇关于NaN的位模式是否真的依赖于硬件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆