2013-08-07 5 views
2

Я читаю Java puzzlers от Joshua Bloch. В головоломке 28, я не в состоянии понять следующее paragraph-Прояснение с плавающей запятой Java

Это работает, потому что чем больше значения с плавающей точкой, тем больше расстояния между значением и его преемником. Это распределение значений плавающей запятой является следствием их представления с фиксированным числом значимых бит . Добавление 1 к значению с плавающей запятой , которое достаточно велико, не изменит значение, поскольку оно не «связывает промежуток» с его преемником.

  1. Почему большие значения с плавающей точкой имеют большие расстояния между их значениями и преемниками?
  2. В случае Integer, мы добавляем один, чтобы получить следующий Integer, но в случае float, как мы можем получить следующее float значение? Если у меня есть значение float в формате IEEE-754, добавьте ли я 1 к части мантиссы, чтобы получить следующий float?

ответ

4

Вы можете думать с плавающей точкой в ​​качестве базовой 2 научной нотации. В плавающей точке вы ограничены фиксированным количеством бит для мантиссы (a.k.a. значение) и для показателя степени. Сколько зависит от того, используете ли вы float (24 бита) или double (53 бит).

Немного более знакомо думать о базовой нотации базы-10. Представьте, что мантисса ограничена целым числом и всегда представлена ​​тремя значащими цифрами. Теперь рассмотрим эти две пары последовательных чисел в этом представлении:

  • 100 х 10 и 101 х 10 (100 и 101)
  • 100 х 10 и 101 х 10 (1000 и 1010)

Обратите внимание, что расстояние (иначе разница) между числами в первой паре равно 1, в то время как со второй парой это 10. в обеих парах, мантиссы различаются на 1, whic h - наименьшая разница между целыми числами, но разность масштабируется экспонентой. Вот почему большие числа имеют большие шаги между ними в плавающей точке (ваш первый вопрос).

Что касается второго вопроса, давайте рассмотрим добавление 1 (100 х 10 -2) на номер 1000 (100 × 10):

  • 100 х 10 + 100 х 10 -2 = 1001 х 10

, но мы ограничены только три значащих цифр в мантиссах, так что последний номер GE ц нормализована (после округления) до:

  • 100 х 10

, который оставляет нас на 1000. Чтобы изменить значение с плавающей точкой, необходимо добавить по крайней мере, половину разницы между это число и следующее число; эта минимальная разница изменяется со шкалой числа.

Точно такая же вещь происходит с бинарной плавающей точкой. Есть более подробная информация (например, нормализация, защитные цифры, подразумеваемая точка счисления, подразумеваемый бит), о которых вы можете прочитать в отличной статье What Every Computer Scientist Should Know About Floating-Point Arithmetic

+0

Отличный ответ, отличная ссылка. Благодаря! –

3
  1. числа с плавающей точкой представлены в виде комбинации мантиссы и экспоненты, где значение числа является mantissa * 2^(exponent) поэтому, если мы предположим, что мантисса ограничивается 2 цифры (чтобы сделать вещи проще) и вы имеют номер 1.1 * 2^100, , который является очень большим, «следующее» значение будет 1.2 * 2^100. поэтому, если вы делаете смешанные вычисления, 1.1*2^100 + 1 будет округленным назад до 1.1*2^100, так как в мантисса недостаточно, чтобы сохранить точный результат.
  2. Начиная с java 6 у вас есть полезный метод Math.nextUp() и Math.nextAfter(), который позволит вам «перебирать» все возможные значения double/float. перед этим вам нужно добавить +1 к мантиссе и по возможности позаботиться о переполнении, чтобы получить следующие/предыдущие значения.
5

Представьте формат на основе десятичной дроби, где вам разрешено устанавливать только первые 5 значений (т. Е. Ваша мантисса - длина 5). Для небольших количествах вы бы хорошо: 1,0000, 12,000, 125,00

Но для большего числа вы бы начать с укоротить e.g.1113500. Следующим представимым числом будет 1113600, что на 100 больше. Любые значения между ними просто не могут быть представлены в этом формате. Если вы читали значение в этом диапазоне, вам нужно было бы усечь его - найти ближайшее представление, которое соответствует, даже если оно не является точным.

Проблема ухудшается, чем больше число. Если я достиг 34567800000, то следующий представимый номер будет 34567900000, который составляет 1000000 или один миллион. Таким образом, вы можете видеть, что разница между представлениями зависит от размера.

С другой стороны, при малых значениях 0.0001 следующее представимое значение равно 0,0002, поэтому разрыв составляет всего 0,0001.

Значения с плавающей запятой имеют тот же принцип, но с двоичной кодировкой (полномочия двух вместо десяти).

2

Хотя это не объясняет причину, этот пример кода показывает, как рассчитать расстояние между поплавком и следующим доступным поплавком и дает пример для большого числа. f и g должны быть Integer.MAX_VALUE друг от друга, но они одинаковы. И следующее значение - h, что составляет 1099511627776 больше.

float f = Long.MAX_VALUE; 
System.out.println("f = " + new BigDecimal(f)); 
System.out.println("f bits = " + Float.floatToIntBits(f)); 
float g = f - Integer.MAX_VALUE; 
System.out.println("g = f - Integer.MAX_VALUE = " + new BigDecimal(g)); 
System.out.println("g bits = " + Float.floatToIntBits(g)); 
System.out.println("f == g? " + (f == g)); 
float h = Float.intBitsToFloat(Float.floatToIntBits(f) + 1); 
System.out.println("h = " + new BigDecimal(h)); 
System.out.println("h bits = " + Float.floatToIntBits(h)); 
System.out.println("h - f = " + new BigDecimal(h).subtract(new BigDecimal(f))); 

выходы:

f = 9223372036854775808 
f bits = 1593835520 
g = f - Integer.MAX_VALUE = 9223372036854775808 
g bits = 1593835520 
f == g? true 
h = 9223373136366403584 
h bits = 1593835521 
h - f = 1099511627776 
Смежные вопросы