Масштабирование большого целого числа двойным

Мне нужно масштабировать большие целые числа (несколько сотен бит) в два раза. В частности, мне нужно вычислить

(М * фактор) мод М

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

Кнут и GMPИсходный код / ​​MPIR не имеет ответов, и здесь я нашел только Умножение между большими целыми и двойными что не совсем применимо, так как второй ответ слишком экзотичен, а первый теряет слишком много точности.

Работая с первых принципов и симулируя большие целые числа с помощью uint64_t, я придумал это (работающий с 64-битным VC ++ или gcc / MinGW64):

#include <cassert>
#include <cfloat>
#include <climits>
#include <cmath>
#include <cstdint>
#include <cstdio>

#include <intrin.h>   // VC++, MinGW

#define PX(format,expression)  std::printf("\n%35s  ==  " format, #expression, expression);

typedef std::uint64_t limb_t;

// precision will be the lower of LIMB_BITS and DBL_MANT_DIG
enum {  LIMB_BITS = sizeof(limb_t) * CHAR_BIT  };

// simulate (M * factor) mod M with a 'big integer' M consisting of a single limb
void test_mod_mul (limb_t modulus, double factor)
{
assert( factor >= 0 );

// extract the fractional part of the factor and discard the integer portion

double ignored_integer_part;
double fraction = std::modf(factor, &ignored_integer_part);

// extract the significand (aligned at the upper end of the limb) and the exponent

int exponent;
limb_t significand = limb_t(std::ldexp(std::frexp(fraction, &exponent), LIMB_BITS));

// multiply modulus and single-limb significand; the product will have (n + 1) limbs

limb_t hi;
/* limb_t lo = */_umul128(modulus, significand, &hi);

// The result comprises at most n upper limbs of the product; the lowest limb will be
// discarded in any case, and potentially more. Factors >= 1 could be handled as well,
// by dropping the modf() and handling exponents > 0 via left shift.

limb_t result = hi;

if (exponent)
{
assert( exponent < 0 );

result >>= -exponent;
}

PX("%014llX", result);
PX("%014llX", limb_t(double(modulus) * fraction));
}

int main ()
{
limb_t const M = 0x123456789ABCDEull;  // <= 53 bits (for checking with doubles)

test_mod_mul(M, 1 - DBL_EPSILON);
test_mod_mul(M, 0.005615234375);
test_mod_mul(M, 9.005615234375);
test_mod_mul(M, std::ldexp(1, -16));
test_mod_mul(M, std::ldexp(1, -32));
test_mod_mul(M, std::ldexp(1, -52));
}

Умножение и сдвиг будут выполняться с помощью целочисленной математики в моем приложении, но принцип должен быть таким же.

Правильный ли базовый подход или тест работает только потому, что я здесь тестирую с целыми числами игрушек? Я не знаю ничего о математике с плавающей запятой, и я выбрал функции из C ++ ссылка.

Пояснение: все, начиная с умножения и далее, будет сделано с (частичной) большой целой математикой; здесь я использую только limb_t для этой цели, чтобы получить крошечную игрушечную программу, которая может быть размещена и действительно запускается. Окончательное приложение будет использовать моральный эквивалент mpn_mul_1 () и mpn_rshift () GMP.

2

Решение

Число с плавающей запятой — не что иное, как произведение трех терминов. Три условия знак, мантисса (иногда называется мантисса) и показатель степени. Значение этих трех терминов вычисляется как

(-1)знак * мантисса * базапоказатель степени

База обычно равна 2, хотя стандарт C ++ не гарантирует этого. Соответственно, ваш расчет становится

(M * фактор) мод M

== (M * (-1)знак * мантисса * базапоказатель степени) мод M

== ((-1)знак(M) + знак * абс (M) * мантисса * базапоказатель степени) мод M

Вычисление знака результата должно быть довольно тривиальным. вычисления Икс * базапоказатель степени довольно просто: это либо подходящий сдвиг битов, если основание равно 2, либо умножение на / деление на степень основания (сдвиг влево или умножение для положительного показатель степени, сдвиг вправо или деление на отрицательное показатель степени). Предполагая, что ваше большое целочисленное представление уже поддерживает операцию модуля, единственным интересным термином является умножение abs (M) * мантисса но это обычное целочисленное умножение, хотя для вашего большого целочисленного представления. Я не проверял слишком близко, но я считать это то, что делает первый ответ, на который вы ссылаетесь (тот, который вы назвали «слишком экзотическим»).

Оставшийся бит должен извлечь знак, мантисса, а также показатель степени из double, Знак можно легко определить путем сравнения с 0.0 и мантисса и показатель степени можно получить с помощью frexp() (увидеть этот ответ например). мантисса возвращается как doubleто есть вы, вероятно, хотите умножить его на 2std::numeric_limits<double>::digits и соответствующим образом скорректировать показатель степени (я давно этого не делал, т. е. не совсем уверен в предварительном договоре frexp()).

Чтобы ответить на ваш вопрос: я не знаком с операциями GMP, которые вы используете, но я думаю, что операции, которые вы действительно выполняете, выполняют вычисления, описанные выше.

4

Другие решения


По вопросам рекламы ammmcru@yandex.ru
Adblock
detector