任意精度算术解释

时间:2009-08-02 04:27:06

标签: math biginteger bignum

我正在尝试学习C并且遇到无法使用真正的大数字(即100位,1000位等)。我知道存在可以执行此操作的库,但我想尝试自己实现它。

我只是想知道是否有人或者可以提供任意精度算术的非常详细,愚蠢的解释。

8 个答案:

答案 0 :(得分:152)

答案 1 :(得分:8)

虽然重新发明轮子非常适合您的个人教育和学习,但它也是一项非常大的任务。我不想劝阻你作为一项重要的练习,也不是我自己完成的练习,但是你应该意识到大型课程所涉及的工作中存在微妙而复杂的问题。

例如,乘法。天真地,你可能会想到“小学生”的方法,即在一个数字之上写一个数字,然后在学校学习时进行长时间的乘法。例如:

      123
    x  34
    -----
      492
+    3690
---------
     4182

但这种方法非常慢(O(n ^ 2),n是位数)。相反,现代bignum包使用离散傅立叶变换或数值变换将其转换为基本上为O(n ln(n))的操作。

这只适用于整数。当你在某种类型的真实数字表示(log,sqrt,exp等)上进入更复杂的函数时,事情变得更加复杂。

如果您有一些理论背景,我强烈建议您阅读Yap的书的第一章"Fundamental Problems of Algorithmic Algebra"。如前所述,gmp bignum库是一个很好的库。对于实数,我使用了mpfr并喜欢它。

答案 2 :(得分:6)

不要重新发明轮子:它可能会变成方形!

使用经过试用和测试的第三方库,例如GNU MP

答案 3 :(得分:4)

你的方式基本上和铅笔和纸一样......

  • 数字将在缓冲区(数组)中表示,可以根据需要采用任意大小(这意味着使用mallocrealloc
  • 使用语言支持的结构尽可能多地实现基本算法,并手动处理携带和移动小数点
  • 您搜索数字分析文本以找到更复杂函数处理的有效参数
  • 你只能根据需要实施。

通常,您将使用基本的计算单位

  • 包含0-99或0-255
  • 的字节
  • 16位字,0-9999或0--65536
  • 包含...的32位字
  • ...

由您的架构决定。

二进制或十进制基数的选择取决于您对最大空间效率,人类可读性以及芯片上是否存在二进制编码十进制(BCD)数学支持的需求。

答案 4 :(得分:3)

你可以用高中数学水平做到这一点。虽然现实中使用了更先进的算法。例如,添加两个1024字节的数字:

unsigned char first[1024], second[1024], result[1025];
unsigned char carry = 0;
unsigned int  sum   = 0;

for(size_t i = 0; i < 1024; i++)
{
    sum = first[i] + second[i] + carry;
    carry = sum - 255;
}
如果要添加以处理最大值,

结果必须更大one place。看看这个:

9
   +
9
----
18
如果你想学习,

TTMath是一个很棒的图书馆。它是使用C ++构建的。上面的例子很愚蠢,但这就是加法和减法一般的做法!

关于这个主题的一个很好的参考是Computational complexity of mathematical operations。它告诉您要实现的每个操作需要多少空间。例如,如果您有两个N-digit个数字,则需要2N digits来存储乘法结果。

正如 Mitch 所说,实施起来并不是一件容易的事!如果您了解C ++,我建议您查看TTMath。

答案 5 :(得分:3)

最终参考之一(恕我直言)是Knuth的TAOCP第二卷。它解释了许多用于表示这些表示的数字和算术运算的算法。

@Book{Knuth:taocp:2,
   author    = {Knuth, Donald E.},
   title     = {The Art of Computer Programming},
   volume    = {2: Seminumerical Algorithms, second edition},
   year      = {1981},
   publisher = {\Range{Addison}{Wesley}},
   isbn      = {0-201-03822-6},
}

答案 6 :(得分:1)

假设您希望自己编写一个大整数代码,这可能会非常简单,最近会做一些人(尽管在MATLAB中。)以下是我使用过的一些技巧:

  • 我将每个十进制数字存储为一个双数字。这使得许多操作变得简单,尤其是输出。虽然它确实占用了比你想象的更多的存储空间,但这里的内存很便宜,如果你能有效地卷积一对矢量,它会使乘法变得非常有效。或者,你可以在一个double中存储几个十进制数字,但要注意那么进行乘法的卷积会导致非常大的数字上的数字问题。

  • 单独存储符号位。

  • 添加两个数字主要是添加数字,然后检查每一步的进位。

  • 一对数字的乘法最好以卷积后跟一个进位步骤进行,至少如果你有一个快速卷积码,那么这就好了。

  • 即使将数字存储为单个十进制数字的字符串,也可以进行除法(也就是mod / rem操作)以在结果中一次获得大约13个十进制数字。这比一次只能处理1位十进制数的除法效率要高得多。

  • 要计算整数的整数次幂,请计算指数的二进制表示。然后使用重复的平方操作来根据需要计算功率。

  • 许多操作(分解,素数测试等)将受益于powermod操作。也就是说,当你计算mod(a ^ p,N)时,在求幂的每一步减少结果mod N,其中p已经以二进制形式表示。不要先计算^ p,然后再尝试将其缩小为N.

答案 7 :(得分:0)

这是我在PHP中做的一个简单(天真)的例子。

我实现了“添加”和“乘法”,并将其用作指数示例。

http://adevsoft.com/simple-php-arbitrary-precision-integer-big-num-example/

代码片段

// Add two big integers
function ba($a, $b)
{
    if( $a === "0" ) return $b;
    else if( $b === "0") return $a;

    $aa = str_split(strrev(strlen($a)>1?ltrim($a,"0"):$a), 9);
    $bb = str_split(strrev(strlen($b)>1?ltrim($b,"0"):$b), 9);
    $rr = Array();

    $maxC = max(Array(count($aa), count($bb)));
    $aa = array_pad(array_map("strrev", $aa),$maxC+1,"0");
    $bb = array_pad(array_map("strrev", $bb),$maxC+1,"0");

    for( $i=0; $i<=$maxC; $i++ )
    {
        $t = str_pad((string) ($aa[$i] + $bb[$i]), 9, "0", STR_PAD_LEFT);

        if( strlen($t) > 9 )
        {
            $aa[$i+1] = ba($aa[$i+1], substr($t,0,1));
            $t = substr($t, 1);
        }

        array_unshift($rr, $t);
     }

     return implode($rr);
}