При делении одного целого числа на другое дробная часть результата отбрасывается. Это не ошибка, так и задумано. Если вы хотите получить результат с плавающей точкой, позаботьтесь о том, чтобы хотя бы один из операндов был числом c плавающей точкой.
3 / 3 # 3
5 / 3 # 1
3 / 4 # 0
3.0 / 4 # 0.75
3 / 4.0 # 0.75
3.0 / 4.0 # 0.75
Если вы работаете с переменными и сомневаетесь относительно их
типа, воспользуйтесь приведением типа к
Float
или методом
to_f
:
z = x.to_f / у z = Float(x) / y
См. также раздел 5.17 «Поразрядные операции над числами».
5.3. Округление чисел с плавающей точкой
Кирк: Какие, вы говорите, у нас шансы выбраться отсюда?
Спок: Трудно сказать точно, капитан. Приблизительно 7824.7 к одному.
Стар Трек, «Миссия милосердия»
Метод
round
округляет число с плавающей точкой до целого:
pi = 3.14159
new_pi = pi.round # 3
temp = -47.6
temp2 = temp.round # -48
Иногда бывает нужно округлить не до целого, а до заданного числа знаков после запятой. В таком случае можно воспользоваться функциями
sprintf
(которая умеет округлять) и
eval
:
pi = 3.1415926535
pi6 = eval(sprintf("%8.6f",pi)) # 3.141593
pi5 = eval(sprintf("%8.5f",pi)) # 3.14159
pi4 = eval(sprintf("%8.4f",pi)) # 3.1416
Это не слишком красиво. Поэтому инкапсулируем оба вызова функций в метод, который добавим в класс
Float
:
class Float
def roundf(places)
temp = self.to_s.length
sprintf("%#{temp}.#{places}f",self).to_f
end
end
Иногда требуется округлять до целого по-другому. Традиционное округление
n+0.5
с избытком со временем приводит к небольшим ошибкам; ведь
n+0.5
все-таки ближе к
n+1
, чем к
n
. Есть другое соглашение: округлять до ближайшего четного числа, если дробная часть равна
0.5
. Для реализации такого правила можно было бы расширить класс
Float
, добавив в него метод
round2
:
class Float
def round2
whole = self.floor
fraction = self — whole
if fraction == 0.5
if (whole % 2) == 0
whole
else
whole+1
end
else
self.round
end
end
end
a = (33.4).round2 # 33
b = (33.5).round2 # 34
с = (33.6).round2 # 34
d = (34.4).round2 # 34
e = (34.5).round2 # 34
f = (34.6).round2 # 35
Видно, что
round2
отличается от
round
только в том случае, когда дробная часть в точности равна 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?
; последний, кстати, вообще не следует переопределять).