web-dev-qa-db-ja.com

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. は次のように述べています。

IEEE 754では、シングルおよびダブル浮動小数点形式ごとに複数の異なるNaN値を使用できます。各ハードウェアアーキテクチャは、新しいNaNが生成されると、NaNの特定のビットパターンを返しますが、プログラマーは、たとえば遡及的な診断情報をエンコードするために、異なるビットパターンでNaNを作成することもできます。

JLSのテキストは、たとえば0.0/0.0の結果がハードウェアに依存するビットパターンを持ち、その式がコンパイル時定数として計算されたかどうかに応じて、依存するハードウェアを暗示しているようです。 Javaプログラムがコンパイルされたハードウェアか、プログラムが実行されたハードウェアである可能性があります。これはすべて、trueの場合非常に不安定なようです。

次のテストを実行しました。

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の間に違いがなかったことも示しています。

私の質問は、コンパイラまたはVMのCPUに関係なく、Javaで出力が保証されているのか、それともすべて本当に予測できないのかということです。 JLSはこれについて不思議です。

その出力が0.0/0.0に対して保証されている場合、他の(おそらくハードウェアに依存する)ビットパターンを持つNaNを生成する算術的な方法はありますか? (intBitsToFloat/longBitsToDoubleが他のNaNをエンコードできることは知っていますが、他の値が通常の算術から発生する可能性があるかどうかを知りたいです。)


フォローアップポイント: Float.NaN および Double.NaN は正確なビットパターンを指定しますが、ソースでは( FloatDouble )それらは0.0/0.0によって生成されます。その除算の結果が本当にコンパイラのハードウェアに依存している場合は、仕様または実装のいずれかに欠陥があるようです。

56
Boann

これは JVM 7仕様の§2.3.2 がそれについて言わなければならないことです:

倍精度値セットの要素は、NaN値が1つしかないことを除いて、IEEE 754標準で定義されている倍精度浮動小数点形式を使用して表すことができる値です(IEEE 754は2を指定します)。53-2つの異なるNaN値)。

および §2.8.1

Java仮想マシンにはシグナリングNaN値がありません。

したがって、技術的にはNaNは1つだけです。しかし JLSの§4.2. も言います(あなたの引用の直後):

ほとんどの場合、Java SEプラットフォームは、特定のタイプのNaN値を単一の正規値に折りたたまれているかのように扱います。したがって、この仕様では通常、任意のNaNを正規値のように参照します。 。

ただし、バージョン1.3のJava SEプラットフォームでは、プログラマーがNaN値を区別できるようにするメソッドFloat.floatToRawIntBitsメソッドとDouble.doubleToRawLongBitsメソッドが導入されました。興味のある読者はFloatの仕様を参照してください。詳細については、Doubleクラスを参照してください。

私があなたと CandiedOrange 提案を正確に意味すると解釈します:それは基礎となるプロセッサに依存しますが、Javaはそれらをすべて同じように扱います。

しかし、それはさらに良くなります。 Double.longBitsToDouble() で説明されているように、NaN値がサイレントに異なるNaNに変換される可能性は完全にあります。

このメソッドは、long引数とまったく同じビットパターンのdoubleNaNを返すことができない場合があることに注意してください。 IEEE 754は、2種類のNaN、クワイエットNaNとシグナリングNaNを区別します。 2種類のNaNの違いは、通常Javaでは表示されません。シグナリングNaNの算術演算は、NaNを、異なるが、多くの場合類似したビットパターンを持つ静かなNaNに変換します。ただし、一部のプロセッサでは、シグナリングNaNをコピーするだけでその変換も実行されます。特に、シグナリングNaNをコピーして呼び出し元のメソッドに返すと、この変換が実行される場合があります。そのため、longBitsToDoubleは、シグナリングNaNビットパターンでdoubleを返すことができない場合があります。したがって、一部の長い値では、doubleToRawLongBits(longBitsToDouble(start))がstartと等しくない場合があります。さらに、どの特定のビットパターンがシグナリングNaNを表すかは、プラットフォームに依存します。ただし、すべてのNaNビットパターンは、クワイエットまたはシグナリングを問わず、上記で特定したNaN範囲内にある必要があります。

参考までに、ハードウェアに依存するNaNの表があります ここ 。要約すれば:

- 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

したがって、これを確認するには、これらのプロセッサの1つが本当に必要であり、試してみる必要があります。また、PowerおよびAlphaアーキテクチャのより長い値を解釈する方法に関する洞察も歓迎します。

36
jmiserez

さまざまなNaNビットパターンを示すプログラムを次に示します。

public class Test {
  public static void main(String[] arg) {
    double myNaN = Double.longBitsToDouble(0x7ff1234512345678L);
    System.out.println(Double.isNaN(myNaN));
    System.out.println(Long.toHexString(Double.doubleToRawLongBits(myNaN)));
    final double zeroDivNaN = 0.0 / 0.0;
    System.out.println(Double.isNaN(zeroDivNaN));
    System.out
        .println(Long.toHexString(Double.doubleToRawLongBits(zeroDivNaN)));
  }
}

出力:

true
7ff1234512345678
true
7ff8000000000000

ハードウェアの機能に関係なく、プログラム自体がNaNを作成する可能性がありますが、これはたとえば、 0.0/0.0、そしてプログラムで何らかの意味を持つかもしれません。

14

ここでJLSを読み取る方法は、NaNの正確なビット値は、誰が/何を作成したかによって異なります。JVMが作成しなかったため、質問しないでください。 「エラーコード4」の文字列が何を意味するのかを尋ねた方がよいでしょう。

ハードウェアは、さまざまな種類のNaNを表すことを目的としたさまざまなビットパターンを生成します。残念ながら、異なる種類のハードウェアは、同じ種類のNaNに対して異なるビットパターンを生成します。幸い、Javaは、少なくともある種のNaNであることを示すために使用できる標準パターンがあります。

Java「エラーコード4」の文字列を見て、「ハードウェアで「コード4」が何を意味するのかわかりませんが、その中に「エラー」という単語がありました」と言ったようなものです。文字列なので、エラーだと思います。」

JLSは、自分でそれを理解する機会を与えようとします。

"ただし、バージョン1.3のJava SEプラットフォームでは、プログラマーがNaN値を区別できるようにするメソッドが導入されました。Float.floatToRawIntBitsおよびDouble.doubleToRawLongBitsメソッド。興味のある読者は、詳細についてFloatおよびDoubleクラスの仕様を参照してください。」

これは私にはC++のように見えますreinterpret_cast。 Java信号がどのようにエンコードされているかを知っている場合に備えて、NaNを自分で分析する機会を提供します。ハードウェアの仕様を追跡して、さまざまなイベントが生成するものを予測できるようにする場合どのNaNビットパターンを自由に使用できますが、JVMが提供することを意図した均一性の範囲外です。したがって、ハードウェアごとに変更される可能性があります。

数値がNaNであるかどうかをテストするとき、それがそうでない唯一の数値であるため、それがそれ自体と等しいかどうかをチェックします。これは、ビットが異なるということではありません。ビットを比較する前に、JVMはNaNであると言う多くのビットパターンをテストします。これらのパターンのいずれかである場合、2つのオペランドのビットが実際に同じであっても(そしてそれらが異なっていても)、等しくないことを報告します。

1964年に、ポルノの正確な定義を求められたとき、米国最高裁判所のスチュワート裁判官は、「私はそれを見るとそれを知っている」と有名に言いました。 JavaはNaNと同じことをしていると思います。Javaは、「シグナリング」NaNがシグナリングしている可能性があることを何も教えてくれません。その信号がどのようにエンコードされたかはわかりませんが、そのパターンは1つの標準に従っているため、ビットを調べて、ある種のNaNであることがわかります。

すべてのNaNを均一なビットでエンコードするハードウェアを使用している場合、JavaがNaNに均一なビットを持たせるために何かをしていることを証明することはできません。繰り返しますが、私がJLSを読む方法では、あなたがここに一人でいるとはっきり言っています。

なぜこれが不安定に感じるのかがわかります。薄片状です。それはJavaのせいではありません。いくつかの進取的なハードウェアメーカーが素晴らしく表現力豊かなシグナリングNaNビットパターンを思いついたが、Javaがそれを信頼できるほど広く標準として採用されなかった、という可能性があります。 。それが不安定なことです。これらのビットはすべて、使用しているNaNの種類を通知するために予約されており、その意味に同意できないため使用できません。Java NaNを打ち負かすハードウェアがそれらを作った後の均一な価値は、その情報を破壊し、パフォーマンスを損なうだけであり、唯一の見返りは不安定に見えないことです。状況を考えると、彼らが問題から抜け出し、NaNをそうではないと定義できることに気付いてうれしいです。何にでも等しい。

14
candied_orange

これまでのところ、通常の算術演算で生成できる他のNaN値は同じですが、符号が変更されています。

public static void main(String []args) {
    Double tentative1 = 0d/0d;
    Double tentative2 = Math.sqrt(-1d);

    System.out.println(tentative1);
    System.out.println(tentative2);

    System.out.println(Long.toHexString(Double.doubleToRawLongBits(tentative1)));
    System.out.println(Long.toHexString(Double.doubleToRawLongBits(tentative2)));

    System.out.println(tentative1 == tentative2);
    System.out.println(tentative1.equals(tentative2));
}

出力:

NaN

NaN

7ff8000000000000

fff8000000000000

false

true

4
falsarella