В моем предыдущем посте по Вопросу Гельфанда рассматривалась проблема, которая требует многократного нахождения первой цифры 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.