Почему преобразование из 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

Ответ 1

Значение с float не изменяется при преобразовании в double. Существует разница в отображаемых цифрах, потому что требуется больше цифр, чтобы отличить double значение от его соседей, что требуется в документации Java. Это документация для toString, на которую ссылаются (через несколько ссылок) из документации для println.

Точное значение для 125.32f составляет 125,3 1999969482421875. Два соседних значения с float - 125,3 199920654296875 и 125,3 2000732421875. Заметьте, что 125.32 ближе к 125.31999969482421875, чем к любому из соседей. Поэтому, отображая "125.32", Java отображала достаточно цифр, так что преобразование обратно из десятичной цифры в число с float воспроизводит значение числа с float переданного в println.

Два соседних double значения 125.3199996948242 1875: 125.3199996948242 045391452847979962825775146484375 и 125.3199996948242 329608547152020037174224853515625.
Обратите внимание, что 125.32 ближе к последнему соседу, чем к исходному значению (125.31999969482421875). Таким образом, печать "125.32" не содержит достаточно цифр, чтобы различить исходное значение. Java должна напечатать больше цифр, чтобы гарантировать, что преобразование отображаемой цифры обратно в double воспроизводит значение double переданное в println.

Ответ 2

  • Когда вы конвертируете float в double, нет никакой потери информации. Каждый float может быть представлен точно как double.
  • С другой стороны, ни одно десятичное представление, напечатанное System.out.println, не является точным значением для числа. Для точного десятичного представления может потребоваться до 760 десятичных цифр. Вместо этого System.out.println печатает точно число десятичных цифр, которые позволяют анализировать десятичное представление обратно в исходное float или double. Существует больше double s, поэтому при печати один, System.out.println должен печатать больше цифр, прежде чем представление станет недвусмысленным.

Ответ 3

Преобразование из float в double является расширяющимся преобразованием, поскольку заданное JLS. Расширяющееся преобразование определяется как инъективное отображение меньшего набора в его супермножество. Поэтому отображаемое число не изменяется после преобразования от float до double.

Дополнительная информация о вашем обновленном вопросе

В вашем обновлении вы добавили пример, который должен продемонстрировать, что число изменилось. Однако это только показывает, что строковое представление числа изменилось, что действительно связано с дополнительной точностью, полученной при преобразовании в double. Обратите внимание, что ваш первый вывод - это просто округление второго выхода. Как указано Double.toString,

Должна быть хотя бы одна цифра, чтобы представлять дробную часть, а кроме того, столько же, сколько нужно, столько цифр, сколько необходимо, чтобы однозначно различать значение аргумента от смежных значений типа double.

Так как смежные значения в типе double намного ближе, чем в float, для соответствия этому правилу требуется больше цифр.

Ответ 4

32-битное число с плавающей запятой IEEE-754, самое близкое к 125.32, фактически составляет 125.31999969482421875. Довольно близко, но не совсем там (потому что 0.32 повторяется в двоичном виде). ​​

Когда вы отбрасываете это в double, это значение 125.31999969482421875, которое будет сделано в double (125.32 нигде не будет найдено на данный момент, информация, что он действительно должен заканчиваться на .32, полностью потеряна) и, конечно же, могут быть представлены точно двойным. Когда вы печатаете этот двойник, программа печати считает, что она имеет более значимые цифры, чем она есть (но, конечно, она не может этого знать), поэтому она печатает до 125.31999969482422, что является самым коротким десятичным числом, которое округляется до этого двойного (и всех десятичных знаков этой длины, это ближайший).

Ответ 5

Проблема точности чисел с плавающей запятой на самом деле является языковой агностикой, поэтому я буду использовать 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

Ответ 6

Как уже объяснялось, все поплавки могут быть точно представлены как двойные, и причина вашей проблемы заключается в том, что System.out.println выполняет некоторое округление при отображении значения float или double, но методология округления не является то же самое в обоих случаях.

Чтобы увидеть точное значение поплавка, вы можете использовать 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

Ответ 7

он не работает в java, потому что по умолчанию java будет принимать реальные значения как double, и если мы объявим значение float без представления float как 123.45f по умолчанию он будет считать его двойным, и это вызовет ошибку как потерю точности

Ответ 8

Представление значений изменяется из-за контрактов методов, которые преобразуют числовые значения в String, соответственно java.lang.Float#toString(float) и java.lang.Double#toString(double), в то время как фактическое значение остается тем же, В Javadoc есть общая часть обоих вышеупомянутых методов, которая разрабатывает требования к String представлению значений:

Должна быть хотя бы одна цифра для представления дробной части, и помимо этого столько, но только столько, сколько цифр необходимо, чтобы однозначно отличить значение аргумента от смежных значений

Чтобы проиллюстрировать сходство значимых частей для значений обоих типов, можно запустить следующий фрагмент:

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.

Ответ 9

Оба являются тем, что Microsoft называет "приблизительными типами данных числа".

Есть причина. Поплавок имеет точность 7 цифр и двойную 15. Но я видел, что это случалось много раз, что 8.0 - 1.0 - 6.999999999. Это связано с тем, что они не гарантируют точно представлять дробную цифру.

Если вам нужна абсолютная неизменная точность, пойдите с десятичным или интегральным типом.