Почему преобразование из float в double изменяет значение?
Я пытался выяснить причину, но я не мог. Кто-нибудь может мне помочь?
Посмотрите на следующий пример.
float f; f = 125.32f; System.out.println("value of f = " + f); double d = (double) 125.32f; System.out.println("value of d = " + d);
Это результат:
- Проблема с поплавком на C
- Драйвер Microsoft ACE изменяет точность с плавающей запятой в остальной части моей программы
- Почему SSE скалярный sqrt (x) медленнее, чем rsqrt (x) * x?
- Как напечатать двойное значение с полной точностью с помощью cout?
- Спецификатор ширины печати для поддержания точности значения с плавающей запятой
значение f = 125,32
значение d = 125.31999969482422
- Каково обоснование для всех сравнений, возвращающих false для значений NaN IEEE754?
- Изменение режима округления с плавающей запятой
- Преобразование из строки научной нотации в float в C #
- «плавающая» и «двойная» точность
- Сравнение чисел с плавающей запятой в C
- Почему числа с плавающей точкой неточны?
- Оптимизация для быстрого умножения, но медленное добавление: FMA и doubleedouble
- Принудительное использование плавающей запятой в .NET?
- Когда вы конвертируете
float
вdouble
, нет потери информации. Каждыйfloat
может быть представлен какdouble
. - С другой стороны, ни одно десятичное представление, напечатанное
System.out.println
является точным значением для числа. Для точного десятичного представления может потребоваться до 760 десятичных цифр. Вместо этогоSystem.out.println
точно печатает число десятичных цифр, которое позволяет анализировать десятичное представление обратно в исходноеfloat
илиdouble
. Их болееdouble
с, поэтому при печати один из нихSystem.out.println
должен печатать больше цифр, прежде чем представление станет недвусмысленным.
Значение float
не изменяется при преобразовании в double
. Существует разница в отображаемых цифрах, потому что для отличия double
значения от его соседей требуется большее количество цифр, что требуется в документации Java . Это документация для toString
, которая передается (через несколько ссылок) из документации для println
.
Точное значение для 125.32f
составляет 125.31999969482421875. Двумя соседними значениями float
являются 125.3199920654296875 и 125.32000732421875. Обратите внимание, что 125.32 ближе к 125.31999969482421875, чем к любому из соседей. Поэтому, отображая «125.32», Java отображает достаточно цифр, чтобы преобразование из десятичной цифры в float
воспроизводило значение float
переданное в println
.
Двумя соседними double
значениями 125.31999969482421875 являются 125.3199996948242045391452847979962825775146484375 и 125.3199996948242329608547152020037174224853515625. Обратите внимание, что 125.32 ближе к последнему соседу, чем к исходному значению. Поэтому печать «125.32» не содержит достаточно цифр для отличия исходного значения. Java должна печатать больше цифр, чтобы гарантировать, что преобразование из отображаемой цифры обратно для double
воспроизведения значения double
переданного в println
.
Преобразование из float
в double
– это расширенное преобразование , как указано JLS . Расширяющееся преобразование определяется как инъективное отображение меньшего набора в его супермножество. Поэтому отображаемое число не меняется после преобразования из float
в double
.
Дополнительная информация о вашем обновленном вопросе
В вашем обновлении вы добавили пример, который должен продемонстрировать, что число изменилось. Тем не менее, это только показывает, что строковое представление числа изменилось, что действительно связано с дополнительной точностью, полученной при преобразовании в double
. Обратите внимание, что ваш первый вывод – это просто округление второго выхода. Как указано Double.toString
,
Должна быть хотя бы одна цифра, чтобы представлять дробную часть, а за ее пределами – столько, сколько нужно, но больше, чем столько цифр, сколько необходимо для однозначного отличия значения аргумента от смежных значений типа
double
.
Поскольку смежные значения в типе double
намного ближе, чем в float
, для соответствия этому правилу требуется больше цифр.
32-битное число с плавающей запятой IEEE-754, самое близкое к 125.32, фактически составляет 125.31999969482421875. Довольно близко, но не совсем там (это потому, что 0.32 повторяется в двоичном виде).
Когда вы накладываете это на double, это значение 125.31999969482421875, которое будет превращено в двойное (125.32 нигде не будет найдено в этот момент, информация, которая должна действительно заканчиваться на .32, полностью потеряна) и, конечно, может быть представлена ровно в два раза. Когда вы печатаете этот двойник, программа печати считает, что она имеет более значимые цифры, чем она есть (но, конечно, она не может этого знать), поэтому она печатает до 125.31999969482422, что является самым коротким десятичным числом, которое округляется до этого двойного (и из всех десятичных знаков этой длины, это самое близкое).
Проблема точности чисел с плавающей запятой на самом деле является языковой агностикой, поэтому я буду использовать 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
Как уже было объяснено, все поплавки могут быть точно представлены как двойные, и причина вашей проблемы в том, что 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
он не работал в java, потому что в java по умолчанию он будет принимать реальные значения как double, и если мы объявим значение float без представления float, например 123.45f, это займет его как двойное, и это вызовет ошибку как потерю точности
Представление значений изменяется из-за контрактов методов, которые преобразуют числовые значения в String
, соответственно java.lang.Float#toString(float)
и java.lang.Double#toString(double)
, в то время как фактическое значение остается неизменным , В Javadoc есть общая часть обоих вышеупомянутых методов, которые разрабатывают требования к представлению String
:
Должна быть хотя бы одна цифра для представления дробной части, а кроме нее столько, но только столько, сколько больше цифр, необходимых для однозначного выделения значения аргумента из смежных значений
Чтобы проиллюстрировать сходство значимых частей для значений обоих типов, можно выполнить следующий fragment:
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 .
Оба являются тем, что Microsoft называет «приблизительными типами данных числа».
Есть причина. Поплавок имеет точность 7 цифр и двойную 15. Но я видел, что это случалось много раз, что 8.0 – 1.0 – 6.999999999. Это связано с тем, что они не гарантируют точно представлять десятичную цифру.
Если вам нужна абсолютная, неизменная точность, используйте десятичный или интегральный тип.