【问题标题】:Scaling a big integer by a double将大整数按双倍缩放
【发布时间】: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


【解决方案1】:

浮点数不过是三项的乘积。 signsignificand(有时称为尾数)和指数这三个项。这三个项的值计算为

(-1)符号 * significand * base指数

基数通常为 2,尽管 C++ 标准不保证这一点。相应地,你的计算就变成了

(M * 因子) mod M

== (M * (-1)sign * significand * base指数) mod M

== ((-1)sign(M) + sign * abs(M) * significand * baseexponent) mod M

计算结果的符号应该相当简单。计算 X * baseexponent 相当简单:如果基数为 2,它要么是适当的位移位,要么是与 / 的乘法除以基数(正指数左移或乘法,负指数右移或除法)。假设你的大整数表示已经支持模运算,唯一有趣的术语是 abs(M) * significand 的乘法,但这只是一个普通的整数乘法,尽管对于你的大整数表示。我没有仔细检查,但我认为这是你链接到的第一个答案(你描述为“太异国情调”的答案)。

剩下的部分是从double 中提取 signsignificandexponent。通过与0.0 的比较可以轻松确定符号,并且可以使用frexp() 获得significandexponent(例如参见this answer)。 significanddouble 的形式返回,也就是说,您可能希望将其乘以 2std::numeric_limits&lt;double&gt;::digits 并适当调整指数(我在一段时间,即我不完全确定frexp()的确切合同。

回答您的问题:我不熟悉您使用的 GMP 操作,但我认为您所做的操作确实执行了上述计算。

【讨论】:

  • freexp() 返回归一化的有效数字和以 2 为底的适当指数;即 0.5 将原样返回,指数将为 0。这里它用于从因子中提取所有有效位。我添加了 modf() 调用,目的是避免大整数模运算;与完整的大整数除法相比,我的应用程序的其余部分仅使用带有大整数的小(肢体大小)模数,这很容易实现。
  • 我认为应该注意的是,double 根据标准没有固定且明确定义的大小。它通常是 64 位,但在其他情况下 double 可以更大,我没有遇到过 double 小于 64 位的情况,但根据标准,尽管有后者仍然是可能的据我所知,基本上没有实现double 的架构少于 64 位。您必须检查 doubles 和 floats 的大小,因为该大小会影响浮点表示的精度。
  • @DarthGizka:是的,有效数字作为分数返回 - 然而,这是你不想要的!它需要作为整数,即,您将缩放它没有非零小数位并转换为合适的整数。乘以有效数时,需要相应调整指数。我注意到我没有完全回答您的问题(补充):我认为您执行的计算完全符合上述计算。
  • @user2485710: // 精度将是 LIMB_BITS 和 DBL_MANT_DIG 中的较低值
  • @DarthGizka 你可能想切换到 &lt;limits&gt; 头文件并使用 std::numeric_limits en.cppreference.com/w/cpp/types/numeric_limits ,看看成员常量和成员函数,它会更惯用C++ 用户。
猜你喜欢
  • 2017-04-14
  • 1970-01-01
  • 1970-01-01
  • 2020-10-11
  • 2017-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多