理由を探ろうとしてきましたが、わかりませんでした。誰かが私を助けてくれますか?
次の例を見てください。
float f = 125.32f;
System.out.println("value of f = " + f);
double d = (double) 125.32f;
System.out.println("value of d = " + d);
これは出力です:
value of f = 125.32
value of d = 125.31999969482422
float
に変換しても、double
の値は変わりません。 double
の値を隣の値と区別するために必要な桁数が増えるため、表示される数値に違いがあります Java documentation で必要です。つまり、ドキュメントです) toString
のドキュメントから(いくつかのリンクを介して)参照されるprintln
の場合。
125.32f
の正確な値は125.31999969482421875です。隣接する2つのfloat
値は、125.3199920654296875および125.32000732421875です。 125.32は、どちらのネイバーよりも125.31999969482421875に近いことを確認します。したがって、「125.32」を表示することにより、Javaは十分な桁数を表示したため、10進数からfloat
への変換によって、float
に渡されたprintln
の値が再現されます。
125.31999969482421875の2つの隣接するdouble
値は、125.3199996948242045391452847979962825775146484375および125.3199996948242329608547152020037174224853515625.
125.32が元の値(125.31999969482421875)よりも後者に近いことを確認します。したがって、「125.32」の印刷には、元の値を区別するのに十分な桁数が含まれていません。 Javaは、表示された数値からdouble
への変換が、double
に渡されたprintln
の値を確実に再現するために、さらに桁を印刷する必要があります。
float
をdouble
に変換しても、情報が失われることはありません。すべてのfloat
は、double
として正確に表すことができます。System.out.println
が出力する10進表記も、数値の正確な値ではありません。正確な10進表記では、最大で約760桁の10進数が必要になる可能性があります。代わりに、System.out.println
は、10進表記を解析して元のfloat
またはdouble
に戻すことができる10進数の桁数を正確に出力します。より多くのdouble
sがあるため、1つを印刷する場合、表現が明確になる前にSystem.out.println
がより多くの桁を印刷する必要があります。float
からdouble
への変換は、拡大変換であり、 JLSで指定 =。拡大変換は、小さいセットからそのスーパーセットへの単射マッピングとして定義されます。したがって、float
からdouble
への変換後、表されている数値は変更されません。
更新では、数が変更されたことを示すはずの例を追加しました。ただし、これは数値のstring表現が変更されたことを示しているだけです。これは、実際にdouble
。最初の出力は2番目の出力の丸めにすぎないことに注意してください。 Double.toString
で指定されているように、
小数部を表すには少なくとも1桁必要です。さらに、引数の値をタイプ
double
の隣接する値と一意に区別するために必要な桁数だけ多くの桁があります。
タイプdouble
の隣接する値はfloat
の値よりもはるかに近いため、その決定に従うためには、さらに多くの桁が必要です。
125.32に最も近い32ビットIEEE-754浮動小数点数は、実際には125.31999969482421875です。かなり近いですが、それほどではありません(0.32がバイナリで繰り返されているためです)。
これをdoubleにキャストすると、doubleに変換される値は125.31999969482421875です(この時点で125.32はどこにも見つからず、実際に.32で終わる必要があるという情報は完全に失われます)。もちろん、表すことができます正確にダブルで。その倍精度浮動小数点数を印刷すると、印刷ルーチンは実際の桁数よりも有効桁数が多いと見なします(ただし、それを知ることはできません)。つまり、125.31999969482422に出力します。これは、その正確な倍精度に丸める最短の10進数です(そしてその長さのすべての小数のうち、最も近いです)。
すでに説明したように、すべての浮動小数点数はdoubleとして正確に表すことができ、問題の理由はSystem.out.println
は、float
またはdouble
の値を表示するときにある程度の丸めを実行しますが、丸めの方法はどちらの場合も同じではありません。
Floatの正確な値を確認するには、BigDecimal
を使用できます。
float f = 125.32f;
System.out.println("value of f = " + new BigDecimal(f));
double d = (double) 125.32f;
System.out.println("value of d = " + new BigDecimal(d));
出力:
value of f = 125.31999969482421875
value of d = 125.31999969482421875
浮動小数点数の精度の問題は、実際には言語に依存しないため、説明ではMATLABを使用します。
違いが見られるのは、特定の数値が固定ビット数で正確に表現できないためです。 0.1
を例にとります:
>> format hex
>> double(0.1)
ans =
3fb999999999999a
>> double(single(0.1))
ans =
3fb99999a0000000
したがって、単精度での0.1
の近似の誤差は、倍精度浮動小数点数としてキャストすると大きくなります。倍精度で直接開始した場合、結果はその近似とは異なります。
>> double(single(0.1)) - double(0.1)
ans =
1.490116113833651e-09
Javaで動作しません。デフォルトではJavaであるため、実際の値はdoubleとして扱われ、123.45fのようなfloat表現のないfloat値を宣言するとデフォルトでは2倍になり、精度の低下としてエラーが発生します
数値をString
に変換するメソッドの契約により、値の表現は変化しますが、実際の値は変わりませんが、Java.lang.Float#toString(float)
およびJava.lang.Double#toString(double)
に対応します。前述の両方のメソッドの共通部分がJavadocにあり、値のString
表現に対する要件を詳しく説明しています。
小数部を表すには、少なくとも1桁が必要です。さらに、引数の値を隣接する値から一意に区別するために必要な桁数だけ、ただしそれ以上の桁数が必要です。
両方のタイプの値の重要な部分の類似性を示すために、次のスニペットを実行できます。
package com.my.sandbox.numbers;
public class FloatToDoubleConversion {
public static void main(String[] args) {
float f = 125.32f;
floatToBits(f);
double d = (double) f;
doubleToBits(d);
}
private static void floatToBits(float floatValue) {
System.out.println();
System.out.println("Float.");
System.out.println("String representation of float: " + floatValue);
int bits = Float.floatToIntBits(floatValue);
int sign = bits >>> 31;
int exponent = (bits >>> 23 & ((1 << 8) - 1)) - ((1 << 7) - 1);
int mantissa = bits & ((1 << 23) - 1);
System.out.println("Bytes: " + Long.toBinaryString(Float.floatToIntBits(floatValue)));
System.out.println("Sign: " + Long.toBinaryString(sign));
System.out.println("Exponent: " + Long.toBinaryString(exponent));
System.out.println("Mantissa: " + Long.toBinaryString(mantissa));
System.out.println("Back from parts: " + Float.intBitsToFloat((sign << 31) | (exponent + ((1 << 7) - 1)) << 23 | mantissa));
System.out.println(10D);
}
private static void doubleToBits(double doubleValue) {
System.out.println();
System.out.println("Double.");
System.out.println("String representation of double: " + doubleValue);
long bits = Double.doubleToLongBits(doubleValue);
long sign = bits >>> 63;
long exponent = (bits >>> 52 & ((1 << 11) - 1)) - ((1 << 10) - 1);
long mantissa = bits & ((1L << 52) - 1);
System.out.println("Bytes: " + Long.toBinaryString(Double.doubleToLongBits(doubleValue)));
System.out.println("Sign: " + Long.toBinaryString(sign));
System.out.println("Exponent: " + Long.toBinaryString(exponent));
System.out.println("Mantissa: " + Long.toBinaryString(mantissa));
System.out.println("Back from parts: " + Double.longBitsToDouble((sign << 63) | (exponent + ((1 << 10) - 1)) << 52 | mantissa));
}
}
私の環境では、出力は次のとおりです。
Float.
String representation of float: 125.32
Bytes: 1000010111110101010001111010111
Sign: 0
Exponent: 110
Mantissa: 11110101010001111010111
Back from parts: 125.32
Double.
String representation of double: 125.31999969482422
Bytes: 100000001011111010101000111101011100000000000000000000000000000
Sign: 0
Exponent: 110
Mantissa: 1111010101000111101011100000000000000000000000000000
Back from parts: 125.31999969482422
このようにすると、仮数が拡張され、その重要な部分(11110101010001111010111
) まったく同じ。