web-dev-qa-db-ja.com

floatからdoubleに変換すると値が変わるのはなぜですか?

理由を探ろうとしてきましたが、わかりませんでした。誰かが私を助けてくれますか?

次の例を見てください。

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
16
arthursfreire

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の値を確実に再現するために、さらに桁を印刷する必要があります。

16
  1. floatdoubleに変換しても、情報が失われることはありません。すべてのfloatは、doubleとして正確に表すことができます。
  2. 一方、System.out.printlnが出力する10進表記も、数値の正確な値ではありません。正確な10進表記では、最大で約760桁の10進数が必要になる可能性があります。代わりに、System.out.printlnは、10進表記を解析して元のfloatまたはdoubleに戻すことができる10進数の桁数を正確に出力します。より多くのdoublesがあるため、1つを印刷する場合、表現が明確になる前にSystem.out.printlnがより多くの桁を印刷する必要があります。
12
Pascal Cuoq

floatからdoubleへの変換は、拡大変換であり、 JLSで指定 =。拡大変換は、小さいセットからそのスーパーセットへの単射マッピングとして定義されます。したがって、floatからdoubleへの変換後、表されている数値は変更されません

更新された質問に関する詳細情報

更新では、数が変更されたことを示すはずの例を追加しました。ただし、これは数値のstring表現が変更されたことを示しているだけです。これは、実際にdouble。最初の出力は2番目の出力の丸めにすぎないことに注意してください。 Double.toStringで指定されているように、

小数部を表すには少なくとも1桁必要です。さらに、引数の値をタイプdoubleの隣接する値と一意に区別するために必要な桁数だけ多くの桁があります。

タイプdoubleの隣接する値はfloatの値よりもはるかに近いため、その決定に従うためには、さらに多くの桁が必要です。

4
Marko Topolnik

125.32に最も近い32ビットIEEE-754浮動小数点数は、実際には125.31999969482421875です。かなり近いですが、それほどではありません(0.32がバイナリで繰り返されているためです)。

これをdoubleにキャストすると、doubleに変換される値は125.31999969482421875です(この時点で125.32はどこにも見つからず、実際に.32で終わる必要があるという情報は完全に失われます)。もちろん、表すことができます正確にダブルで。その倍精度浮動小数点数を印刷すると、印刷ルーチンは実際の桁数よりも有効桁数が多いと見なします(ただし、それを知ることはできません)。つまり、125.31999969482422に出力します。これは、その正確な倍精度に丸める最短の10進数です(そしてその長さのすべての小数のうち、最も近いです)。

3
harold

すでに説明したように、すべての浮動小数点数は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
1
assylias

浮動小数点数の精度の問題は、実際には言語に依存しないため、説明では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
1
Amro

Javaで動作しません。デフォルトではJavaであるため、実際の値はdoubleとして扱われ、123.45fのようなfloat表現のないfloat値を宣言するとデフォルトでは2倍になり、精度の低下としてエラーが発生します

0
sekhar beri

数値を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) まったく同じ。

使用される浮動小数点数部分の抽出ロジック: 1 および 2

0
Pavel