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

Это результат:

значение f = 125,32

значение d = 125.31999969482422

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

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

  • плавать до неожиданного поведения
  • Печать двойная без потери точности
  • Проверка, является ли float целым числом
  • C ++: как округлить двойной к int?
  • Самый быстрый способ сделать горизонтальную векторную сумму float на x86
  • Разделение целых чисел на Java
  • разбиение числа на целые и десятичные числа
  • Как представить номер FLOAT в памяти на C
  • Печать чисел с плавающей запятой из x86-64, по-видимому, требует сохранения% rbp
  • Представление целых чисел в двухместных
  • В каком порядке следует добавлять поплавки, чтобы получить наиболее точный результат?
  • Давайте будем гением компьютера.