Потеря точности - int -> float или double - PullRequest
23 голосов
/ 06 мая 2010

У меня есть экзаменационный вопрос, для которого я пересматриваю, и вопрос для 4 баллов.

«В java мы можем присвоить int двойному или плавающему». Будет ли это когда-либо терять информацию и почему?

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

Теперь я немного отрывочен относительно того, бью ли я по нужным областям или нет. Я уверен, что он потеряет точность, но я не могу точно понять, почему. Могу ли я получить помощь, пожалуйста?

Ответы [ 8 ]

49 голосов
/ 06 мая 2010

В Java Integer использует 32 бита для представления своего значения.

В Java FLOAT использует 23-битную мантиссу, поэтому у целых чисел больше 2 ^ 23 их младшие биты будут усечены. Например, 33554435 (или 0x200003) будет обрезано до 33554432 +/- 4

В Java DOUBLE использует 52-битную мантиссу, поэтому сможет представлять 32-битное целое число без потери данных.

См. Также " Плавающая точка " в Википедии

24 голосов
/ 13 мая 2010

Нет необходимости знать внутреннюю разметку чисел с плавающей запятой.Все, что вам нужно, это принцип «квадратного отверстия» и знание того, что int и float имеют одинаковый размер.

  • int - это 32-битный тип, для которого каждая битовая комбинация представляет отдельныйцелое число, поэтому существует 2 ^ 32 int значений.
  • float - это 32-битный тип, поэтому он имеет не более 2 ^ 32 различных значений.
  • Некоторые floats представляют нецелые числа, поэтому меньше , чем 2 ^ 32 float значений, представляющих целые числа.
  • Следовательно, различные значения int будут преобразованы в одинаковые float(= потеря точности).

Аналогичные рассуждения можно использовать с long и double.

18 голосов
/ 06 мая 2010

Вот что говорит JLS по этому вопросу (в нетехническом обсуждении).

JLS 5.1.2 Расширяющее примитивное преобразование

Следующие 19 конкретных преобразований примитивных типов называются расширяющимися примитивными преобразованиями:

  • int до long, float или double
  • (остальные опущены)

Преобразование значения int или long в float или значения long в double может привести к потере точности , то есть результат может потерять некоторые из младших значащих битов значения. В этом случае результирующее значение с плавающей запятой будет правильно округленной версией целочисленного значения с использованием режима округления до ближайшего стандарта IEEE 754.

Несмотря на то, что может произойти потеря точности, расширяющиеся преобразования среди примитивных типов никогда не приводят к исключению во время выполнения.

Вот пример расширяющегося преобразования, которое теряет точность:

class Test {
         public static void main(String[] args) {
                int big = 1234567890;
                float approx = big;
                System.out.println(big - (int)approx);
        }
}

который печатает:

-46

, что указывает на то, что информация была потеряна при преобразовании из типа int в тип float, поскольку значения типа float не являются точными до девяти значащих цифр.

13 голосов
/ 06 мая 2010

Нет, float и double тоже фиксированной длины - они просто используют свои биты по-разному. Узнайте больше о том, как именно они работают, в Floating-Poing Guide .

По сути, вы не можете потерять точность при назначении int для double, поскольку double имеет 52 бита точности, что достаточно для хранения всех значений int. Но float имеет только 23 бита точности, поэтому он не может точно представлять все int значения, которые больше чем примерно 2 ^ 23.

4 голосов
/ 17 июня 2014

Ваша интуиция верна, вы МОЖЕТЕ потерять точность при преобразовании int в float. Однако это не так просто, как представлено в большинстве других ответов.

В Java FLOAT использует 23-битную мантиссу, поэтому у целых чисел больше 2 ^ 23 их младшие значащие биты будут усечены. (из поста на этой странице)

Не соответствует действительности.
Пример: здесь целое число больше 2 ^ 23, которое преобразуется в число с плавающей точкой без потерь:

int i = 33_554_430 * 64; // is greater than 2^23 (and also greater than 2^24); i = 2_147_483_520
float f = i;
System.out.println("result: " + (i - (int) f)); // Prints: result: 0
System.out.println("with i:" + i + ",  f:" + f);//Prints: with i:2_147_483_520,  f:2.14748352E9

Следовательно, неверно, что целые числа больше 2 ^ 23 будут обрезать свои младшие значащие биты.

Лучшее объяснение, которое я нашел здесь:
Число с плавающей точкой в ​​Java является 32-битным и представлено как:
знак * мантисса * 2 ^ экспонента
знак * (от 0 до 33_554_431) * 2 ^ (- от 125 до +127)
Источник: http://www.ibm.com/developerworks/java/library/j-math2/index.html

Почему это проблема?
Создается впечатление, что вы можете определить, есть ли потеря точности от int до float , просто взглянув на то, насколько велико int.
Я особенно видел экзаменационные вопросы по Java, где спрашивают, превратится ли большое int в float без потерь.

Кроме того, иногда люди склонны думать, что от int до float будет потеря точности:
когда int больше чем: 1_234_567_890 не верно (см. контрпример выше)
когда int больше чем: 2 экспонента 23 (равно: 8_388_608) не соответствует действительности
когда int больше чем: 2 экспонента 24 (равно: 16_777_216) не верно

Заключение
Преобразования из достаточно больших целых чисел в числа с плавающей точкой МОГУТ потерять точность.
Невозможно определить, будут ли убытки, просто , посмотрев , насколько велико значение int (т.е. не пытаясь углубиться в реальное представление с плавающей точкой).

4 голосов
/ 10 мая 2013

Возможно, самое ясное объяснение, которое я видел: http://www.ibm.com/developerworks/java/library/j-math2/index.html ULP или единица наименьшей точности определяет доступную точность между любыми двумя значениями с плавающей запятой. По мере увеличения этих значений доступная точность уменьшается. Например: между 1,0 и 2,0 включительно имеется 8 388 609 чисел с плавающей запятой, между 1 000 000 и 1 000 001 - 17. При 10 000 000 ULP составляет 1,0, поэтому выше этого значения у вас скоро будет несколько целочисленных значений, сопоставляемых каждому доступному числу с плавающей точкой, что приводит к потере точности.

1 голос
/ 06 мая 2010

Есть две причины, по которым присваивание типа int для double или float может потерять точность:

  • Существуют определенные числа, которые просто не могут быть представлены как double / float, поэтому они в конечном итоге приближаются
  • Большие целые числа могут содержать слишком много точности в значимых для аренды цифрах
0 голосов
/ 15 декабря 2017

В этих примерах я использую Java.

Используйте функцию, подобную этой, чтобы проверять потерю точности при приведении от int к float

static boolean checkPrecisionLossToFloat(int val)
{
  if(val < 0)
  {
    val = -val;
  }
  // 8 is the bit-width of the exponent for single-precision
  return Integer.numberOfLeadingZeros(val) + Integer.numberOfTrailingZeros(val) < 8;
}

Используйте функцию, подобную этой, чтобы проверить потерю точности при приведении от длинного к двойному

static boolean checkPrecisionLossToDouble(long val)
{
  if(val < 0)
  {
    val = -val;
  }
  // 11 is the bit-width for the exponent in double-precision
  return Long.numberOfLeadingZeros(val) + Long.numberOfTrailingZeros(val) < 11;
}

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

static boolean checkPrecisionLossToFloat(long val)
{
  if(val < 0)
  {
    val = -val;
  }
  // 8 + 32
  return Long.numberOfLeadingZeros(val) + Long.numberOfTrailingZeros(val) < 40;
}

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

Приведение к плавающей точке теряет точность, если целое значение имеет более 24 значащих бит.

Преобразование в удвоенное значение приведет к потере точности, если целое значение имеет более 53 значащих бит.

...