Статьи

Ведущие цифры и четырехместные

В моем  предыдущем посте по Вопросу Гельфанда  рассматривалась проблема, которая требует многократного нахождения первой цифры  k n,  где k  — это одна цифра, но  n  может быть порядка миллионов или миллиардов.

Наиболее прямым подходом было бы сначала вычислить  k n  как очень большое целое число, а затем найти его первую цифру. Этот подход медленный и становится медленнее с   ростом n . Более быстрый способ — посмотреть на дробную часть log  k n  =  n  log  k  и посмотреть, какой цифре она соответствует.

Если  n  не очень большое, это можно сделать с обычной точностью. Но когда  n  большое, умножение log k  на  n  и взятие дробной части приводит к значению менее значимых цифр. Поэтому для очень большого  n вам нужна дополнительная точность. Сначала я сделал это в Python с использованием SymPy, а затем переключился на C ++ для большей скорости. Там я использовал  quadmath библиотеку для  gcc. (Если  n  достаточно велико, даже четверной точности недостаточно. Преимущество SymPy по сравнению с тем  quadmath , что первое имеет  произвольную точность. Например, можно установить точность на 10 больше, чем число десятичных разрядов в  n чтобы сохранить 10 значащих цифр в дробной части  n  log  k .)

quadmath.h Заголовочный файл должен быть обернут в  extern C декларации. В противном случае  gccвы получите вводящие в заблуждение сообщения об ошибках.

128-битный тип __float128 с плавающей запятой  имеет в два раза больше битов, чем a  double. Эти  quadmathфункции имеют такое же имя , как и их стандартные  math.h аналоги, но с  q добавлена на конце, например , как  log10q и  fmodq ниже.

Вот код для вычисления старшей цифры  k n,  который иллюстрирует использование  quadmath.

#include <cmath>
extern "C" {
#include <quadmath.h>
}
 
__float128 logs[11];
 
for (int i = 2; i <= 10; i++)
    logs[i] = log10q(i + 0.0);
 
int first_digit(int base, long long exponent)
{
    __float128 t = fmodq(exponent*logs[base], 1.0);
    for (int i = 2; i <= 10; i++)
        if (t < logs[i])
            return i-1;
}

Код всегда возвращается, потому что  t меньше 1.

Кэширование значений  log10q сохраняет повторные вызовы относительно дорогой функции. То же самое делает поиск внизу, а не вычисления  powq(10, t).

Линейный поиск в конце более эффективен, чем может показаться. Во-первых, это только поиск по списку длиной 9. Во-вторых, по закону Бенфорда ведется поиск first_digit начальных цифр в порядке убывания частоты, то есть большинство входных данных приведут  к раннему поиску.

Когда вы компилируете код, используя  quadmath, обязательно добавьте  -lquadmath команду compile.