#c #алгоритм #binary
На 32-bit микропроцессоре нужна функция (эффективный алгоритм) для получения частного и остатка от деления uint64_t на 10 (как всегда для печати). Полазив по интернету, в http://www.hackersdelight.org/divcMore.pdf нашел лишь нечто похожее (для uint32_t) с вот таким кодом unsigned divu10(unsigned n) { unsigned q, r; q = (n >> 1) + (n >> 2); q = q + (q >> 4); q = q + (q >> 8); q = q + (q >> 16); q = q >> 3; r = n - q*10; return q + ((r + 6) >> 4); // return q + (r > 9); } Для своей цели изменил ее вот так (фактически, добавил оператор q = q + (q >> 32); : uint64_t divu64_10 (uint64_t n, uint32_t *rem) { uint64_t q, r; q = (n >> 1) + (n >> 2); q = q + (q >> 4); q = q + (q >> 8); q = q + (q >> 16); q = q + (q >> 32); q = q >> 3; r = n - ((q << 3) + (q << 1)); *rem = r > 9 ? r - 10 : r; // orig: return q + ((r + 6) >> 4); return q + (r > 9); } Проверил, вводя разные числа вот в таком коде на нормальном 64-бит компе: unsigned long long v; while (scanf("%llu", &v) == 1) { unsigned long long d; uint32_t r; d = divu64_10(v, &r); printf("divu10: %llu %u (C: %llu %llu)\n", d, r, v / 10, v % 10); } Похоже, мое изменение работает. Вопрос, собственно в следующем -- может ли кто-нибудь в самом деле разбирающийся в двоичной арифметике подтвердить, что divu64_10() работает верно?
Ответы
Ответ 1
Разберем как работает исходный код. Он вычисляет q = n * 0x33333333 >> 33, что примерно равно n * 0.0999999999.... Затем вычисляет ошибку n - q*10, и если она меньше 10, то всё правильно, если больше 10, то к результату добавляется 1. Расмотрим значения ошибок. 10 * 0x33333333 / 2 ** 33 = 0b00.111111111111111111111111111111110 11 * 0x33333333 / 2 ** 33 = 0b01.000110011001100110011001100110001 19 * 0x33333333 / 2 ** 33 = 0b01.111001100110011001100110011001001 20 * 0x33333333 / 2 ** 33 = 0b01.111111111111111111111111111111100 21 * 0x33333333 / 2 ** 33 = 0b10.000110011001100110011001100101111 ... 65530 * 0x33333333 / 2 ** 33 = 6552.999998474261 = 0b1100110011000.111111111111111111100110011001110 ... 4294967290 * 0x33333333 / 2 ** 33 = 429496728.9 = 0b11001100110011001100110011000.111001100110011001100110011001110 Видно что для n = 10*d + r, ошибка равна 10 если r=0. Можно посчитать, что если бы вместо сдвига было бы деление, то ошибка была бы равна n - n * 10 * 0x33333333 / 2**33 = n / 2 ** 32 Так как n < 2 ** 32, то ошибка результата не превышает единицу. Для 64 разрядов всё так же, только считаем n * 0x3333333333333333 >> 65. Добавление q += q >> 32 эквивалентно умножению на (2**32+1)/2**32, при этом (2**32+1)*0x33333333 = 0x3333333333333333.
Комментариев нет:
Отправить комментарий