только в том случае, когда дробная часть в точности равна 0.5. Отметим, кстати, что число 0.5 можно точно представить в двоичном виде. Не так очевидно, что этот метод правильно работает и для отрицательных чисел (попробуйте!). Отметим еще, что скобки в данном случае необязательны и включены в запись только для удобства восприятия.
Ну а если мы хотим округлять до заданного числа знаков после запятой, но при этом использовать метод «округления до четного»? Тогда нужно добавить в класс
Float
также метод
roundf2
:
class Float
# Определение round2 такое же, как и выше.
def roundf2(places)
shift = 10**places
(self * shift).round2 / shift.to_f
end
end
a = 6.125
b = 6.135
x = a.roundf2(a) #6.12
y = b.roundf2(b) #6.13
У методов
roundf
и
roundf2
есть ограничение: большое число с плавающей точкой может стать непредставимым при умножении на большую степень 10. На этот случай следовало бы предусмотреть проверку ошибок.
5.4. Сравнение чисел с плавающей точкой
Печально, но факт: в компьютере числа с плавающей точкой представляются неточно. В идеальном мире следующий код напечатал бы «да», но на всех машинах где мы его запускали, печатается «нет»:
x = 1000001.0/0.003
y = 0.003*x
if y == 1000001.0
puts "да"
else
puts "нет"
end
Объясняется это тем, что для хранения числа с плавающей точкой выделено конечное число битов, а с помощью любого, сколь угодно большого, но конечного числа битов нельзя представить периодическую десятичную дробь с бесконечным числом знаков после запятой.
Из-за этой неустранимой неточности при сравнении чисел с плавающей точкой мы
можем оказаться в ситуации (продемонстрированной выше), когда с практической точки зрения два числа равны, но аппаратура упрямо считает их различными.
Ниже показан простой способ выполнения сравнения с «поправкой», когда числа считаются равными, если отличаются не более чем на величину, задаваемую программистом:
class Float
EPSILON = 1e-6 # 0.000001
def == (x)
(self-x).abs < EPSILON
end
end
x = 1000001.0/0.003
y = 0.003*x
if y == 1.0 # Пользуемся новым оператором ==.
puts "да" # Теперь печатается "да".
else
puts "нет"
end
В зависимости от ситуации может понадобиться задавать разные погрешности. Для этого определим в классе
Float
новый метод
equals?
. (При таком выборе имени мы избежим конфликта со стандартными методами
equal?
и
eql?
; последний, кстати, вообще не следует переопределять).
class Float
EPSILON = 1e-6
def equals?(x, tolerance=EPSILON)
(self-x).abs < tolerance
end
end
flag1 = (3.1416).equals? Math::PI # false
flag2 = (3.1416).equals?(Math::PI, 0.001) # true
Можно также ввести совершенно новый оператор для приближенного сравнения, назвав его, например,
=~
.
Имейте в виду, что это нельзя назвать настоящим решением. При последовательных вычислениях погрешность накапливается. Если вам совершенно необходимы числа с плавающей точкой, смиритесь с неточностями (см. также разделы 5.8 и 5.9).
5.5. Форматирование чисел для вывода
Для вывода числа в заданном формате применяется метод
printf
из модуля Kernel. Он практически не отличается от одноименной функции в стандартной библиотеке С. Дополнительную информацию см. в документации по методу
printf
.
x = 345.6789
i = 123
printf("x = %6.2f\n", x) # x = 345.68
printf("x = %9.2e\n", x) # x = 3.457e+02
printf("i = %5d\n\ i) # i = 123
printf("i = %05d\n", i) # i = 00123
printf("i = %-5d\n\, i) # i = 123
Чтобы сохранить результат в строке, а не печатать его немедленно, воспользуйтесь методом