Точное значение float 0.7f
составляет 0.69999...
.
поэтому я думал, что результат 0.7f * 100f
будет чем-то ниже 70
, например 69.99999...
Но результат точен 70
.
Включает ли умножение с плавающей запятой такое округление?
Если да, то применима ли такая постобработка и для фиксированной точки?
Я нашел в некоторых библиотеках с фиксированной запятой FP(100) * FP(0.7) is 69.99999
.
При приведении этого значения к int они безжалостно обрезаются, и я получаю 69
. Что нежелательно, поскольку ФП может выражать точные 70
.
Этот вопрос похож на: Не работает ли математика с плавающей запятой?. Если вы считаете, что это другое, отредактируйте вопрос, поясните, чем он отличается и/или как ответы на этот вопрос не помогают решить вашу проблему.
@chtz Думаю, это другой вопрос. Связанный вопрос касается самого представления с плавающей запятой, а мой — о том, как происходит округление при умножении.
Двоичная точка с плавающей запятой конечной точности не может точно представлять каждое действительное (или каждое десятичное) число, но она всегда должна представлять ближайшее возможное число.
Арифметика с плавающей запятой конечной точности также не может вычислить все возможные результаты, но требуется (по крайней мере, в обычных случаях) для вычисления правильно округленного результата.
Точное (ближайшее представимое) число с плавающей запятой одинарной точности, соответствующее 0,7, равно 0b0.101100110011001100110011
, что при обратном преобразовании в десятичное число равно 0,699999988079071044921875. Обратите внимание, что это число имеет 24 значащих бита, что является частью определения одинарной точности IEEE-754.
Умножив это число на 100, мы получим 0b1000101.1111111111111111111011
. Но это число имеет 29 значащих битов, поэтому оно не умещается в 24 бита значения, доступных в одинарной точности. Поэтому нам придется округлить это. Теперь 25-й бит равен 1, поэтому мы округляем вверх, и почти все биты слева от этого 25-го бита равны 1, поэтому округление происходит до 0b1000110.00000000000000000
, или ровно до 70,0.
Обратите внимание, что хотя число с плавающей запятой одинарной точности имеет 24 бита значимости, вычисления с плавающей запятой одинарной точности должны временно выполняться с использованием более 24 бит точности, чтобы можно было точно вычислить несколько битов после 24-го, поэтому что результат можно правильно округлить по мере необходимости.
И это хороший пример того, как работают правила IEEE-754 о правильном округлении результатов, и работают хорошо. Легко создать (ошибочное) впечатление, что значения с плавающей запятой всегда немного неправильны, если не совсем неправильны. Но на самом деле арифметика с плавающей запятой IEEE-754 обычно довольно точна и изо всех сил старается предотвратить накопление ошибок — а это означает, что нередко ошибки могут компенсировать друг друга, в конце концов давая точные результаты. Вот что в основном здесь происходит.
Или, другими словами, правило не в том, что вычисления с плавающей запятой всегда неточны. Настоящее правило заключается в том, что вычисления с плавающей запятой иногда неточны, но иногда они также совершенно точны.
(На самом деле, еще лучшее правило состоит в том, что вычисления с плавающей запятой часто бывают неточными по сравнению с ожидаемым, но десятичным результатом. Если вы возьмете два двоичных числа с плавающей запятой и выполните над ними операцию, вы почти всегда получите результат действительно точный — в двоичном формате. Я хочу сказать, что большинство очевидных неточностей возникает только тогда, когда вы сравниваете двоичный результат с тем, который вы вычислили другим способом, в десятичном формате.)
Если приведенное выше объяснение вам не подходит, вот другой способ взглянуть на него. Как мы знаем, представления с плавающей запятой не могут представлять все числа. Действительно, одно из чисел, которые они не могут представить в двоичном виде, — это 0,7, а ближайшее представимое число с одинарной точностью — это двоичное число, равное 0,699999988079071044921875.
А что, если мы возьмем это число 0,699999988079071044921875 и умножим его на 100? Точный результат должен быть 69,9999988079071044921875. Но это другое число, которое невозможно представить точно. Если вы возьмете непредставимое число 69,9999988079071044921875 и спросите, каким будет ближайшее число, которое можно представить точно с одинарной точностью, то это число будет... 70,0! В одинарной точности следующее представимое число, меньшее этого, равно 69,99999237060546875, что дальше (в данном случае более чем в 5 раз дальше), чем 70,0.
Вы также спросили о библиотеках с фиксированной запятой. Там ответ явно будет зависеть от реализации, но что еще важнее – от базы. Двоичная библиотека с фиксированной запятой также не может точно представлять 0,7. Но десятичная библиотека с фиксированной запятой, очевидно, могла бы.
Я думаю, что двоичное представление 0,7 с фиксированной запятой «8,8» будет 0000000010110011
(основание 2), или 00b3
(основание 16) или 179 (основание 10), что, преобразованное обратно в дробь, равно 0,69921875. Умножив это на 100, получим 100010111101100
/45ec
/17900, что снова преобразуется в 69,921875. Итак, исходя из этих предположений, я не вижу способа добиться большего (то есть способа получить 70,0).
Но если бы десятичная библиотека с фиксированной запятой дала вам 69 после умножения, я бы сказал, что она реализована довольно плохо. 16-битное десятичное представление с фиксированной запятой 0,7 с масштабным коэффициентом 100 будет равно 70, что при умножении на 100 дает 7000, что при повторном делении на масштабный коэффициент, очевидно, дает ровно 70,0.
Для меня очевидный вопрос: почему округление распространяется на все 24 бита, а не просто останавливается, как «0b110.11111111111111111110». Но я думаю, ответ на этот вопрос содержится в правилах IEE-754.
@PeterM Боюсь, я не понимаю, что вы имеете в виду: это не округление. Если вы вычислите промежуточный результат 0b110.1111111111111111111111
с 25 битами и округлите его до 24 бит, округление должно распространиться на все биты вверх, давая 0b111.000000000000000000000
. (В противном случае это было бы просто усечение.) [Кроме того, я изменил цифры в ответе после того, как вы прокомментировали, потому что ОП спрашивал об умножении на 100, а не на 10.]
«округление должно распространяться». В этом суть моего вопроса. Я могу сказать «округлить десятичное число до x значащих цифр», и будет подразумеваться, что округление остановится на x. Если вместо этого я скажу «округлить двоичное число до 24 бит», почему это означает (в данном случае), что округление распространяется на все биты? Я не знаю, является ли это семантикой глагола «округлять» для десятичных и двоичных чисел или этого требуют правила IEE-754.
@PeterM Если кто-то даст мне число 6,9999999999 и попросит округлить его до 5 знаков после запятой, я скажу, что мой ответ - 7,00000. В подобных случаях перенос, подразумеваемый округлением, может распространяться по всем цифрам/битам, но не «останавливается». Это верно как для десятичных, так и для двоичных чисел. (Именно поэтому 0,999… равно 1.
@Питер: Нет, в десятичном формате то же самое. Возьмите 7,9999947 и округлите его до четырех значащих цифр. Вы получаете 8.000. Никакой другой ответ не имеет смысла. Керри должны распространяться.
@SteveSummit Теперь я вижу ошибку в своих мыслях. Я предполагал, что десятичное число не состоит из десятичных девяток, но двоичное число фактически состоит из них. Таким образом, я сравнивал яблоки с апельсинами.
Спасибо за подробный ответ. 25-битное округление было для меня моментом ага. Я все еще немного смущен умножением с фиксированной точкой. Могу ли я сказать, что умножение с фиксированной запятой не может выполняться с более высокой точностью, как с плавающей запятой? (потому что это исправлено)
@yahoic Меня также озадачивает контраст с арифметикой с фиксированной запятой; Я не могу объяснить, почему, как я выразился, не существует возможности «ошибок, нейтрализующих друг друга». Это может быть невезение (то есть могут быть другие проблемы с фиксированной запятой, где округление может помочь), или может быть, плавающая запятая имеет здесь неотъемлемое преимущество, возможно, что (будучи плавающей) она сохраняет большую точность для меньших значений. числа типа 0,7.
Если вы умножите число с фиксированной точкой на целое число (и не измените точность результата), округления не произойдет. Простой пример десятичной дроби с 3+3
цифрами: 1/6 --> 0.167
и 0.167 * 6.000 = 1.002
. Вы не можете сказать, что фиксированная точка «менее точна», чем плавающая. Например. если вы знаете, что работаете только с числами в интервале (-2, +2) и у вас есть 32 бита для хранения, вы можете использовать 2+30
двоичные цифры, что дает вам абсолютную точность 2^-30
, что лучше, чем (относительная) точность из float32
.
@yahoic Я думаю, объяснение в том, что в фиксированной точке нет возможности получить промежуточный результат с избыточной точностью и, следовательно, никогда не будет возможности округлить. В фиксированной точке, если у вас есть N-битный результат и вы умножаете его на целое число, вы либо получите еще один N-битный результат, либо переполнение. С другой стороны, в случае с плавающей запятой, если вы умножаете число с N битами точности на другое число с N битами точности, вы получаете промежуточный результат с 2N битами точности, поэтому вам практически всегда нужно округлять.
Другой способ взглянуть на это заключается в том, что в фиксированной точке все числа имеют (по определению) одинаковую абсолютную точность. С другой стороны, в случае с плавающей запятой, когда вы увеличиваете число (скажем, умножая его на число больше 1), абсолютная точность снижается, поэтому, вероятно, потребуется округление.
В двоичном формате с плавающей запятой одинарной точности числа в диапазоне 0,7 имеют точность ±6×10⁻⁸, а числа в диапазоне 70 имеют точность ±7,6×10⁻⁶. (Это числа FLT_EPSILON / 2
и FLT_EPSILON * 64
соответственно.)
69.99999...
также не совсем представимо какfloat
, поэтому его необходимо округлить. Для некоторых десятичных дробей100 * 0.xy
округляется ровно доxy
, но на это нельзя полагаться. Если вы хотите точно представить0.7
, не используйте двоичные числа с плавающей запятой.