Этот сайт — моя персональная записная книжка. Интересна мне, по большей части, история, своя жизнь и немного программирование.

Ускорение тригонометрических и прочих функций в bc

Интересно, что вся стандартная библиотека (подключаемая через ключ -l) для bc написана нативно. С одной стороны, это не очень-то здорово, так как не использует всю мощь современных процессоров для вычисления различных функций, с другой стороны, эти функции можно свободно переопределить, если мы готовы пожертвовать точностью.

Я тут потихоньку ускоряю одну из своих программ на bc, но все «низко висящие фрукты» я уже подобрал. Добрался до тригонометрических функций. Во внутренней библиотеке косинус выражается через синус, так что, если ускорить синус, то ускорится сразу и косинус.

Посмотрел разные реализации, в итоге портировал функцию, которую взял из одной из статей на «Хабре», получилось так (используется многочлен Чебышёва):

define mod(a, b) {
    auto s, m
    s = scale; scale = 0; m = a$ % b$; scale = s; return m
}

define s(x) {
    auto sign, xf, per, xx, y

    x *= 0.63661977236758134308
    if (sign = (x < 0)) x = -x

    xf = x$
    x -= xf

    if (mod(xf, 2)) x = 1 - x

    per = mod(xf / 2, 2)
    xx = x * x
    y = x * (1.5707903005870776 + xx * (-0.6458858977085938 + \
    xx*(0.07941798513358536 - 0.0043223880120647346 * xx)))

    if (sign != per) return -y
    return y
}

Расхождение со встроенной в bc реализацией начинается в седьмом знаке, это более чем достаточно для меня, зато вычисление синуса ускорилось больше, чем в четыре раза.

Добавлено позднее: ещё я заменил функцию нахождения логарифма по основанию десять. Логарифмом я находил количество разрядов числа, чтобы потом вывести его на экран, так что его легко было заменить на цикл с подсчётом порядка делением. Разница в производительности — больше, чем в 60 раз. Код теперь выглядит так:

define cnt_digits(x) {
    auto i, s
    s = scale; scale = 0

    for (i = 0; x /= 10; i++){}
    scale = s; return i
}
4 комментария
hsh 1 мес

Логарифмом я находит

Евгений Степанищев 1 мес

Спасибо, поправил! Писал уже перед сном.

Oleg Gorbunov 1 мес

Помню, как в старых флеш-играх нужно было таблицу синусов запекать на старте, иногда считать было слишком медленно )

Евгений Степанищев 1 мес

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

Роман Парпалак 1 мес

Для корня из суммы квадратов есть линейное приближение с погрешностью не хуже 4%, может подойдет: https://youtu.be/NWBEA2ECX-A?si=j7rAWOfaS-eAD6NK

Евгений Степанищев 1 мес

О, спасибо, попробую завтра!

Добавлено: Не выдержал, попробовал. В производительности разница в восемь раз, но будет ли достаточной точность для игры — непонятно, надо будет пробовать. Расхождение для некоторых вычисляемых значений у меня получилось ≈ 5,5, это как будто многовато.

ash 1 мес

Для графики я использовал таблицу синусов от 0 до 90 градусов с шагом 1 градус — то есть простая таблица из 90 строк позволяла вычислить любой синус\косинус за линейное время с точностью достаточной для мониторов тех лет (все равно синус с двумя знаками после запятой и синус с десятью знаками — попадут на один и тот же пиксел). Для современных мониторов шаг нужно увеличить до 0.1 градуса, наверное.

Евгений Степанищев 1 мес

Да, можно закешировать вычисление, как я это сделал для квадратного корня.