Рубиновая точность поплавка

Насколько я понимаю, числа с плавающей запятой Ruby (1.9.2) имеют точность 15 знаков после запятой. Поэтому я ожидаю, что округление числа с плавающей запятой x до 15 знаков после запятой будет равно x. Для данного расчета это не так.

x = (0.33 * 10)
x == x.round(15) # => false

Кстати, округление до 16 знаков возвращает true.

Не могли бы вы объяснить это мне?


person Community    schedule 05.09.2011    source источник
comment
По всей вероятности, ваш язык использует IEEE 754 числа с плавающей запятой двойной точности для внутреннего использования. 15 десятичных цифр являются приблизительными, поскольку числа с плавающей запятой на самом деле представлены в базе 2, а не 10.   -  person Pascal Cuoq    schedule 06.09.2011
comment
числа с плавающей запятой такие плавающие :)   -  person fl00r    schedule 06.09.2011
comment
Вопрос про плавающую точность, который я не хотел близко забивать! +1.000000000001!   -  person Andrew Grimm    schedule 06.09.2011
comment
Интересно, почему вы можете == плавающие числа, это ловушка.   -  person Reactormonk    schedule 09.05.2012


Ответы (2)


Частично проблема заключается в том, что 0,33 не имеет точного представления в базовом формате, поскольку его нельзя выразить набором из 1 / 2n терминов. Таким образом, при умножении на 10 умножается число, немного отличающееся от 0,33.

Если на то пошло, 3.3 также не имеет точного представления.

Часть первая

Когда числа не имеют точного представления по основанию 10, будет остаток при преобразовании младшей значащей цифры, для которой была информация в мантиссе. Этот остаток будет распространяться вправо, возможно, навсегда, но в значительной степени это бессмысленно. Очевидная случайность этой ошибки связана с той же причиной, которая объясняет явно непоследовательное округление, которое заметили вы и Matchu. Это во второй части.

Часть вторая

И эта информация (самые правые биты) не выровнена четко с информацией, передаваемой одной десятичной цифрой, поэтому десятичная цифра обычно будет несколько меньше, чем ее значение было бы, если бы исходная точность была выше.

Вот почему преобразование может округляться до 1 при 15 цифрах и до 0.x при 16 цифрах: потому что более длинное преобразование не имеет значения для битов справа от конца мантиссы.

person DigitalRoss    schedule 05.09.2011
comment
Спасибо за вашу помощь. Я понял общую идею, но я все еще пытаюсь доказать это себе. Если Ruby хранит только около 15 значащих цифр, почему sprintf(%.50f,1.1) печатает ненулевые значения после 15-го знака после запятой? Откуда берутся эти значения? - person ; 06.09.2011
comment
+1 Ура, кто-то знает подробности! Тот факт, что Ruby хочет показывать другие десятичные разряды при принуждении к нему, похоже, указывает на то, что он округляет некоторые из них в to_s и, фактически, сохраняет другую информацию помимо того, что он показывает по умолчанию. - person Matchu; 06.09.2011
comment
Джордж ... способ преобразования заключается в том, что значение делится, а затем остаток преобразуется в цифры дальше вправо. Таким образом, если это случайно не достигает нуля, преобразование может производить ненулевые цифры в течение некоторого времени после исчерпания битов мантиссы. Это может продолжаться вечно для повторяющихся частных. Я предполагаю, что кто-то мог бы придумать алгоритм для преднамеренного сужения до нуля, но учтите, что преобразованные таким образом числа будут дальше от реального числа, чем числа с остаточными битами в конце. Лучше вообще не конвертировать лишние цифры. - person DigitalRoss; 07.09.2011
comment
Я также должен добавить, что эта проблема также возникает для иррациональных чисел и для чисел, которые действительно имеют большую точность, чем может указать мантисса. - person DigitalRoss; 07.09.2011
comment
Спасибо, теперь я понял. Я решил посмотреть, что делает C#; он отказывается показывать более 17 знаков после запятой, даже когда его просят указать 50. Для 1.1 он показывает только 16 знаков после запятой. (1.1D).ToString(G50).Длина == 18 - person ; 09.09.2011

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

 > x = (0.33 * 10)
=> 3.3000000000000003
 > x.round(15)
=> 3.300000000000001

По какой-то причине первое число с плавающей запятой сохраняет 16 знаков после запятой, всего 17 цифр. Таким образом, округление до 15 отбрасывает эти цифры.

person Matchu    schedule 05.09.2011
comment
Почему 3.3000000000000003.round(15) == 3.3000000000000001, а не 3.3? Это просто из-за двоичного представления и ошибок округления с плавающей запятой? - person ; 06.09.2011
comment
@georgehemmings: Да, действительно, это связано с тем, что двоичное представление 0,1 (или кратное ему) не является конечной серией из 1/2 ^ n членов. - person Rudy Velthuis; 06.09.2011