【问题标题】:Fast calculation of floating 1/N if factorization of very large integer N is known如果知道非常大的整数 N 的因式分解,则快速计算浮点 1/N
【发布时间】:2021-12-01 03:07:12
【问题描述】:

如果我有整数 N 我知道因式分解,将 1/N 计算为浮点数的最快(最有效)方法是什么?应该使用大浮点(或整数)算法。

我想在 C++ 中执行此操作(或在 Python 中进行实验运行)。

我的N 非常大,有千兆/兆位大小。生成的浮点 N 也应该具有很高的精度,与初始 N 的位大小大致相同。

需要精确的浮点值意味着如果我请求浮点精度Log2(N) 位,那么至少结果的所有95% 前导位都应该是精确的(所有位都与理想值相同)。

当然,如果它有助于和/或简化任务,则可以将4^Ceil(Log2(N)) / N 计算为整数除法,而不是浮点计算。对我来说,这两个任务(整数和浮点数)本质上是相同的,因为整数表示可以转换为浮点数,反之亦然。

一个重要的注意事项是,N 的因式分解只有很小的质因数,它们都是 32 位大小(当然最多可能是 64 位)。

我想知道是否对N 进行了因式分解以及因数很小的事实,它能否以某种方式帮助解决任务?

当然,我没有首先实现我自己的部门,而是尝试使用高度优化的GMP 库来完成这项任务,但它(据我所知)并没有使用N 已经被分解的事实。

任何人都可以建议我是否要为此实现自己的功能,只是为了通过实验确定它是否会比 GMP 更快,那么我应该使用哪种算法?

我发现这里可以使用 3 种算法 1) Long Division,这是一种学校级算法。 2)Barrett Reduction。 3)Montgomery Reduction.

其实我不知道其他算法。你能推荐其他的吗? Barrett 和 Montgomery 约简只有在相同的素因数重复多次时才有帮助,否则单次除法不值得 Barrett 和 Montgomery 所需的预先计算。

此外,Barrett/Montgomery 减少仍需要一次性计算 4^Ceil(Log2(N)) / PrimeDivisor。所以他们不会让你免于做长除法算法。

对于长除法算法,我将使用2^64 作为基础而不是基础10(就像在学校一样)。

我已经使用长除法和所有其他整数算术算法以及椭圆曲线算术实现了我自己的实验库。现在它是通用的,因此不比 GMP 快。现在我需要一个比 GMP 至少快几倍的特殊除法算法。

当然,在长除法中我可以使用蒙哥马利和巴雷特,因为在每一步都需要短除法(128 位整数除以 64 位整数),如果它有任何提升的话。

在长除法的每一步,我都可以使用Fast Fourier TransformNumber Theoretic Transform 进行乘法运算。

以上是我所知道的唯一优化。还有其他可能的优化吗?或许FFT可以直接做除法(不只是乘法)?

【问题讨论】:

  • 我建议你首先让你的算法产生正确的结果,不管它运行多慢。然后在它上面运行一个分析器,看看你需要注意的慢的部分在哪里。在没有看到实际代码的情况下很难给出关于性能的建议
  • @EyalK。当前的问题不是如何优化我所拥有的。我已经有了通用代码。但它并不比 GMP 的通用变体快,这是显而易见的。优化我当前算法的速度没有意义,因为通用解决方案不会比 GMP 更快。这里的问题是如何使用分解可行的事实来实现真正快速的算法。也许有人可以判断基于 FFT 的除法技术是否可用。我不需要比 GMP 快 2 倍的解决方案,我需要一些特殊的算法,如果分解有帮助的话,它会快 10-20 倍。
  • 您应该删除“性能”标签并添加 FORTRAN。 FORTRAN 在数字方面一直很高效。可能有一个您可以使用的 C++ 库。如果不是 FORTRAN,Algol 语言也擅长数字运算。
  • @ThomasMatthews 通过添加 performance 标签,我的意思是我需要一些高性能的算法,特别是比不考虑 N 分解的常规方法(如 GMP)更快。这个标签并不意味着有人应该帮助我在我现有的代码中做一些小的优化。换句话说,我需要一些出色的想法来实现良好和高性能的算法。也许Fortran实际上有这种基于因子的划分。
  • @Ripi2 例如,对于乘法,存在 FFT(傅里叶),它可以达到 O(N Log(N)) 时间,这比 O(N^2) 学校级算法要好得多,如果你有 Tera,这是一个非常巨大的改进位数。但 FFT 只对大数快速。也许类似地,对于长除法,存在比天真的长除法快得多的非常快的算法,但它只对非常大的数字才快。我只是不知道是否存在这种情况,但不久前我也不知道 FFT,但它确实存在。所以等待数学/算法大师回复!

标签: python c++ algorithm performance math


【解决方案1】:

总体思路

N 可以分解为小整数这一事实会有所帮助。实际上,这意味着N = a1 * a2 * ... * an。因此,1/N = 1/(a1 * a2 * ... * an) = (1/a1) * (1/a2) * ... * (1/an)

事情是计算1/a 的许多因素a 可以比计算1/N 更快。确实:

  • 1 / a十进制/二进制扩展的周期应该远小于1 / N,因为log2(a) 远小于log2(N),这意味着1/a 的精确表示可以以非常紧凑的方式快速存储;
  • 因子的倒数可以独立计算,所以并行
  • 当一个因子在 N 的因式分解中多次使用时,其代价高昂的倒数只能计算一次;
  • 乘数应该比计算倒数或除法要快得多
  • 最终的基于乘法的归约可以使用成对归约策略并行完成。

但是,有一个问题:这种方法需要很多乘法,因为有很多因素,虽然它们大部分可以并行计算,但这仍然是一个相当昂贵的计算(尤其是最后一个很难的乘法并行执行)。因此,我不确定该方法是否比在 GMP 中实现的非常优化的方法快得多,但肯定值得一试。


注意事项

最好将相同大小的数字相乘,甚至将第一个数字与最小的十进制/二进制扩展周期相乘(为了最小化计算倒数的符号表示的大小,从而导致更小的内存足迹和更快的乘法)。

大约有 2 亿个素数适合 32 位,但 N 的大多数因子应该适合 16 位(因为较小的因子更频繁)​​并且只有大约 6500 个素数适合16 位,因此如果您计划计算不同 N 的多个倒数,它们的倒数可以预先计算

e 很大时,您可以使用exponentiation by squaring 算法有效地计算p^e 的倒数。当N 很大时,这样的项经常以N 的素数分解的指数形式出现。

如果十进制/二进制扩展的周期大于最终的浮点数,您可以截断它。不过,这可能会影响方法的准确性。我认为至少需要一些额外的数字才能获得准确的结果(尤其是由于应用了许多乘法)。

对于涉及大量符号表示的最后乘法,您可以生成大浮点数并将它们提供给 GMP,以便它可以使用非常优化的实现(通常基于快速傅里叶变换)。


示例

为了清楚起见,这是一个小N = 307230 使用十进制表示的示例(小数扩展的句点带有下划线):

1/307230 = 0.0000032548904729355857175406047586498714318263190443641571461120333300784428603977476157927285746834619015070142889691761872213
              ------------------------------------------------------------------------------------------------------------------------------

307230 = 2 * 3 * 5 * (7^2) * 11 * 19

1/2 = 0.50
         -
1/3 = 0.3
        -
1/5 = 0.20
         -
1/7 = 0.14285714
          ------
1/11 = 0.090
          --
1/19 = 0.0526315789473684210
          ------------------

k1 = ((1/2) * (1/3)) * ((1/5) * (1/11)) = 0.0030
                                          --

k2 = k1 * (1/19) = 0.000159489633173843700
                        ------------------

k3 = (1/7)^2 = 0.0204081632653061224489795918367346938775510
                  ------------------------------------------

1/N = k2 * k3

对于最终乘法,您可以使用精确算法或 GMP 的快速近似。

【讨论】:

  • 我是赞成票之一,但由于对中间结果的精度要求,我怀疑这将输给mpn_invertappr
  • 投票。谢谢你的好答案。可能很快就会批准,给其他答案一些时间。关于您的解决方案有两个问题 - 1) 如何证明它足以保证在单个周期之前有数字的倒数,因此最终的乘法结果将是准确的,它与乘法时完全相同超过句点数字(例如两个句点)? 2) 怎么找保期?例如,如果你得到像0.123456456456 这样的第一个数字,那么保证周期是456 而不是更长的时间是什么?还有1/N 有句号吗?
  • 1/a 的周期长度最大为a-1,例如 375292573(素数)的倒数有一个长度为 375292572 的二进制周期。所以这可能行不通.
  • @n.1.8e9-where's-my-sharem。您知道是否有一种有保证且快速的方法来找出确切的经期长度吗?因为如果我逐位计算一个数字并且有类似0.123456456456 的东西,我不能保证456 已经是一个句点,因为之后可能会去一些其他数字。除了检查所有a-1 位,就像你对1/a 所说的那样,我还能如何找出句号?
  • @Arty 1/a 的周期长度是multiplicative order of 2 mod a(2 是基数;对于基数为 10 的分数,您需要 10 mod a 的顺序)。 This 是一个多语言乘法阶计算器(不保证很快)。
猜你喜欢
  • 1970-01-01
  • 2011-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-01
相关资源
最近更新 更多