为什么BigInteger实现使用符号幅度而不是二进制补码?

时间:2018-08-28 06:18:37

标签: math data-structures binary biginteger arbitrary-precision

几乎总是使用符号幅度表示来实现任意精度的带符号整数:

  • OpenJDK中的(Java)BigInteger
  • (Python)Bigint在CPython中实现Python内置int类型的实现
  • (C)mpz_t在GMP(GNU多精度算术库)中
  • Matt McCutchen在bigint库中的
  • (C ++)BigInteger
  • num-bigint库中的
  • (锈)BigInt

对符号幅度的明确偏好与在固定宽度的带符号整数类型中对二进制补码的近乎普遍的偏好形成对比。问题是,为什么对BigIntegers而言,信号量级如此明显?(如果您不同意前提,我欢迎提出反例。)

请注意,对于重要的按位操作,BigInteger API通常会指定“假设为两个的补码”语义(例如JavaPython)。这与这些操作的通常含义保持了一致。这并不决定实际的内部表示形式(仅是实现细节),但是如果所有其他条件都相等,则应该在内部使用二进制补码。

浮点数使用符号幅度,与使用二进制补码的整数不同。但是,由于浮点算术的行为和算法与整数算术明显不同,因此,浮点数实际上并不是指导性的先例。巨数更像是整数而不是浮点数。

我们知道“教科书”的原因,即为什么补码在数学上起作用以及为何具有优势。在我看来,这些原因对int和BigIntegers同样有效。这在多大程度上是真的?

当然,硬件固定精度整数和软件任意精度整数的设计约束之间存在巨大差异。从这个意义上讲,看到设计师在这些不同领域做出不同的折衷一点也不奇怪。那么,应用于任意精度整数的符号大小和二进制补码之间的权衡是什么?例如,这可能是出于某些重要算法的性能或简单性的考虑。

我希望您的回答能阐明BigInteger算术中的设计注意事项,并帮助我从新的角度重新审视对补数的了解。

(要明确:当我说任意精度整数的二进制补码时,我的意思是使用一组单词组成的表示,其位模式放在一起时就是所需数字的二进制补码表示-也许

,并附加要求没有“不必要的前导0”(对于非负数)或“不必要的前导1”(对于负数)。

2 个答案:

答案 0 :(得分:4)

Two的补码使相等长度的数字的加法和减法更简单,但使乘法和除法更复杂。对于硬件实施,可能会有时间限制,但并非总是如此。查看X86“ Ivy Bridge”指令表,唯一的出现二进制补码而花费更多时间的情况是128位带符号除数除以64位带符号的除数。因此,这主要是基于软件的数学问题。

大整数库可能对大数使用更复杂但速度更快的表示形式。这里是一些示例文章的链接:

https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic

https://cp-algorithms.com/algebra/big-integer.html

http://www.apfloat.org/ntt.html

对于数量较大的数字,较复杂的方法通常更快,对于中等大小的数字,较简单的实现将更快。

答案 1 :(得分:0)

当我建立几个自己的bignum库时,我同意 rcgldr 的答案(+1)二进制补码不仅在*,/上还带来了更高操作的问题。

最重要的是,某些bignum库并不使用2的幂作为基础,为此使用二进制补码也是很麻烦的。不使用2的功能的原因是我们在基数10中进行计算,因此我们期望得到输入并得到这样的结果。 conversion between base 2 (or power of 2) and base 10是IIRC ~O(n^2)任务,对于很大的数字,它通常比对其执行的操作花费更多。因此,这些库使用10的最大功能来适应所用的ALU单词...例如,在32位中,它是1 000 000 000,这虽然浪费了一点空间,但是却减轻了输入和将数字和字符串表示形式之间的转换输出到O(n)。其中n是所使用的数字或单词的数量...

另外,二进制补码使许多底层操作(如multiplication by NTT

)所需的模运算复杂化

二进制补码处理和恢复将花费O(n),而单独的符号只是O(1),这就是我认为的主要原因。

相关问题