【发布时间】:2014-10-18 14:06:27
【问题描述】:
我需要将大整数(几百位)放大一倍。特别是,我需要计算
(M * 因子) mod M
其中 M 是一个大整数,factor 是一个双精度数。除非您想将头文件中的十几行代码称为“库”,否则我不会使用任何库;因此大浮点数学在这里不是一个选项。
Knuth 和 GMP/MPIR 源代码没有答案,在这里我发现只有 Multiplication between big integers and doubles 并不真正适用,因为第二个答案太奇特而且第一个失去了太多精度。
从第一原理开始并使用 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++ reference 中挑选了函数。
澄清:从乘法开始的所有事情都将使用(部分)大整数数学来完成;在这里,我只使用limb_t 来获得一个可以发布并实际运行的小型玩具程序。最终的应用程序将使用 GMP 的 mpn_mul_1() 和 mpn_rshift() 的道德等效项。
【问题讨论】:
-
我不完全确定这个问题是否有意义。假设
M类似于 17,factor是 10^500。那么double的精度不够高,没有有效的单位,所以没有希望计算出正确的答案。 -
对于正在考虑的问题 - (M * f) mod M - 结果必须介于 0 和 (M - 1) 之间。如果有人通过 10^500,那么他们将获得应得的精度。也就是说,随着指示的更改 - 即丢失 modf() 调用并处理从 frexp() 返回的正指数的情况 - 应该正确放大大整数。 (注意:从“limb_t hi”开始的部分将是大整数数学)。
标签: c++ floating-point biginteger arbitrary-precision