Поскольку логарифм используется как в дивиденде, так и в делителе, нет необходимости использовать log()
; мы можем использовать log2()
. Из-за ограничений на входы u
и p
логарифмы, как известно, оба отрицательные, поэтому мы можем ограничить себя вычислением положительной величины -log2()
.
Мы можем использовать арифметику с фиксированной точкой для вычисления логарифма. Мы делаем это, умножая исходный ввод на последовательность факторов убывающей величины, которые приближаются 1. Рассматривая каждый из факторов в последовательности, мы умножаем вход только теми факторами, которые приводят к приближению продукта к 1, но не превышают его. При этом мы суммируем log2()
факторов, которые «подходят». В конце этой процедуры мы завершаем число, очень близкое к 1 как наш конечный продукт, и сумму, представляющую двоичный логарифм.
Этот процесс известен в литературе как мультипликативная нормализация или псевдоделение, а некоторые ранние публикации, описывающие его, - это работы Де Лугиша и Меггита. Последнее указывает, что происхождение в основном является методом Генри Бриггса для вычисления общих логарифмов.
B. de Lugish. «Класс алгоритмов автоматической оценки функций и вычислений на цифровом компьютере». Кандидатская диссертация, кафедра компьютерных наук, Университет Иллинойса, Урбана, 1970.
J. E. Meggitt. «Процессы псевдоделения и псевдопроцесса». IBM Journal of Research and Development, Vol. 6, № 2, апрель 1962, стр. 210-226
В качестве выбранного набора факторов включает в себя 2 я и необходимые умножения могут быть выполнены без необходимости (1 +--i) инструкция умножения: продукты могут быть вычислены сдвигом или сдвигом плюс добавление.
Поскольку входы u
и p
являются чисто дробными числами с 16 битами, мы можем выбрать результат с фиксированной точкой 5.16 для логарифма. Просто разделив два значения логарифма, мы удалим масштабный коэффициент с фиксированной точкой и применим операцию floor()
в то же время, потому что для положительных чисел floor(x)
идентичен trunc(x)
10, а целочисленное деление усекается.
Обратите внимание, что вычисление логарифма с фиксированной запятой приводит к большой относительной ошибке для входов около 1.Это, в свою очередь, означает, что вся функция, рассчитанная с использованием арифметики с фиксированной точкой, может выдавать результаты, значительно отличающиеся от ссылки, если p
невелик. Примером этого является следующий тестовый пример: u=55af p=0052 res=848 ref=874
.
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
/* input x is a 0.16 fixed-point number in [0,1)
function returns -log2(x) as a 5.16 fixed-point number in (0, 16]
*/
uint32_t nlog2_16 (uint16_t x)
{
uint32_t r = 0;
uint32_t t, a = x;
/* try factors 2**i with i = 8, 4, 2, 1 */
if ((t = a << 8 ) < 0x10000) { a = t; r += 0x80000; }
if ((t = a << 4 ) < 0x10000) { a = t; r += 0x40000; }
if ((t = a << 2 ) < 0x10000) { a = t; r += 0x20000; }
if ((t = a << 1 ) < 0x10000) { a = t; r += 0x10000; }
/* try factors (1+2**(-i)) with i = 1, .., 16 */
if ((t = a + (a >> 1)) < 0x10000) { a = t; r += 0x095c0; }
if ((t = a + (a >> 2)) < 0x10000) { a = t; r += 0x0526a; }
if ((t = a + (a >> 3)) < 0x10000) { a = t; r += 0x02b80; }
if ((t = a + (a >> 4)) < 0x10000) { a = t; r += 0x01664; }
if ((t = a + (a >> 5)) < 0x10000) { a = t; r += 0x00b5d; }
if ((t = a + (a >> 6)) < 0x10000) { a = t; r += 0x005ba; }
if ((t = a + (a >> 7)) < 0x10000) { a = t; r += 0x002e0; }
if ((t = a + (a >> 8)) < 0x10000) { a = t; r += 0x00171; }
if ((t = a + (a >> 9)) < 0x10000) { a = t; r += 0x000b8; }
if ((t = a + (a >> 10)) < 0x10000) { a = t; r += 0x0005c; }
if ((t = a + (a >> 11)) < 0x10000) { a = t; r += 0x0002e; }
if ((t = a + (a >> 12)) < 0x10000) { a = t; r += 0x00017; }
if ((t = a + (a >> 13)) < 0x10000) { a = t; r += 0x0000c; }
if ((t = a + (a >> 14)) < 0x10000) { a = t; r += 0x00006; }
if ((t = a + (a >> 15)) < 0x10000) { a = t; r += 0x00003; }
if ((t = a + (a >> 16)) < 0x10000) { a = t; r += 0x00001; }
return r;
}
/* Compute floor(log(u)/log(1-p)) for 0 < u < 1 and 0 < p < 1,
where 'u' and 'p' are represented as 0.16 fixed-point numbers
Result is an integer in range [0, 1048676]
*/
uint32_t func (uint16_t u, uint16_t p)
{
uint16_t one_minus_p = 0x10000 - p; // 1.0 - p
uint32_t log_u = nlog2_16 (u);
uint32_t log_p = nlog2_16 (one_minus_p);
uint32_t res = log_u/log_p; // divide and floor in one go
return res;
}
Как вы представляете 'u' и' p'? –
Ну, я не сейчас, так как я этого не решал. В идеале, они будут 'float', но они недоступны, поэтому' uint16_t' является кандидатом ... – mqtthiqs
Этот вопрос немного широк, тогда (особенно, поскольку «не на 100% точно» может означать что угодно ...). Вы можете использовать 'uint16_t' для представления (например,' 'u * 65536'. Тогда вы (теоретически) могли бы использовать таблицу поиска, которая возвращает 'ln (u)'. Затем вы можете реализовать целочисленное деление. –