Округление до шага

Опубликовано в QLua

Часто требуется математическое действие, округляющее определенное значение к заданному шагу. Такого рода действия необходимы при написании торговых роботов, чтобы в частности привести значение к шагу цены инструмента или округлить текущее время к границе таймфрейма. Например, необходимо округлить число 9876.54321 до шага 0.01. Результат будет 9876.54. Действие тривиальное, но "больная голова рукам покоя не даёт. Улыбаюсь...  

Все ниже сказанное относится к округлению "вниз" (вариант floor). Вариант ceil аналогичен.

Везде встречается следующее решение для языка lua:

function floor_to_step(what,step)
     return math.floor(what/step) * step
end

Оно замечательно работает. Собственно я его всегда и использовал. Но ведь задачу можно решить и иначе, верно?

function floor_to_step(what,step)
   return what - what % step
return

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

local b
local s1,s2
s1 = os.clock()
for i=1,100000000 do
b = i - (i%87.56565656)
end
s1 = os.clock() - s1
s2 = os.clock()
for i=1,100000000 do
b = math.floor(i/87.565656) * 87.565656
end
s2 = os.clock() - s2
print(tostring(s1) .. " " .. tostring(s2))

Округляем до произвольно взятого (с потолка) шага 87.56565656 сто миллионов чисел первым и вторым способом и замеряем время выполнения.

Результат выполнения на моем ноутбуке таков:

5.796     18.528

Что мы получаем? Способ с использованием вызова библиотечной функции проигрывает способу с вычитаем остатка от деления в 3 с лишним раза.

В принципе такой результат можно было предвидеть: дополнительное время тратится на вызов библиотечной функции, да и набор действий сложнее (деление и умножение занимают больше процессорного времени, чем вычитание и взятие остатка).

Осталось удостовериться, что оба метода дают одинаковый результат в числах ( у нас все же вычисления идут в арифметике плавающей точки). Просто удостоверимся, написав простейший тест.

local x,y,z1,z2
for i = -10000000,1000000 do
x = math.random(1,100000)
y = math.random(1,100000)

z1 = x - x%y
z2 = math.floor(x/y) * y
if z1 ~= z2 then
print(tostring(z1) .. " " .. tostring(z2))
end
end

Если хотя бы один раз результаты не сойдутся, мы получим на экране сообщение об этом.

Запускаем - и тишина. Расхождений нет. Да здравствуем мы все!

 

Конечно, такого рода различия имеют смысл лишь в случае, когда округление до шага используется в программе постоянно. Разовое вычисление никак не повлияет на производительность скрипта или программы.

 

  

 


 

См. также О пользе inline-кода или библиотека bit

 

Комментарии   

# Алексей Ч 18.02.2017 02:30
Отлично, спасибо за идею!
В третьем листинге если math.floor вынести в локальную переменную вне цикла, то преимущество сократиться с 3 до 2.
...и еще... никак не соображу, как же написать функцию для округления вверх.
Ответить | Ответить с цитатой | Цитировать
# admin 18.02.2017 09:38
Алексей, для ceil придется использовать оператор if.
Ответить | Ответить с цитатой | Цитировать
# fimkin 07.03.2017 19:10
или what+-what%step
Ответить | Ответить с цитатой | Цитировать

Добавить комментарий


Защитный код
Обновить

Архив QLua