Почему преобразование из 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. Это связано с тем, что они не гарантируют точно представлять дробную цифру.
Если вам нужна абсолютная неизменная точность, пойдите с десятичным или интегральным типом.