在 C++ 中转换 Big Integer <-> double
Posted
技术标签:
【中文标题】在 C++ 中转换 Big Integer <-> double【英文标题】:Conversion Big Integer <-> double in C++ 【发布时间】:2012-09-07 18:42:30 【问题描述】:为了好玩,我正在用 C++ 编写自己的长算法库,它已经完成了,我什至用该库实现了几个 Cryptogrphic 算法,但仍然缺少一件重要的事情:我想转换双精度数(和浮点数/长双打)到我的号码,反之亦然。我的数字表示为一个可变大小的无符号长整数数组加上一个符号位。
我试图用谷歌找到答案,但问题是人们很少自己实现这些东西,所以我只找到关于如何使用 Java BigInteger 等的东西。
从概念上讲,这相当容易:我取尾数,将其移动由指数指定的位数并设置符号。在另一个方向上,我将其截断以使其适合尾数并根据我的 log2 函数设置指数。
但是我很难弄清楚细节,我可以玩弄一些位模式并将其转换为双精度,但我没有找到一种优雅的方法来实现这一点,或者我可以“计算”它从 2 开始,取幂,乘法等,但这似乎不是很有效。
我希望有一个不使用任何库调用的解决方案,因为我试图避免为我的项目使用库,否则我可以只使用 gmp,此外,我经常在其他几个场合有两种解决方案,一种使用内联高效且更独立于平台的汇编程序,因此任何一个答案对我都有用。
编辑:我使用 uint64_t 作为我的部件,但我希望能够根据机器进行更改,但我愿意使用一些#ifdefs 做一些不同的实现来实现这一点。
【问题讨论】:
避免任何库调用是否包括对标准库(尤其是数学库)的调用? 也许看看这段代码会对你有所帮助:gist.github.com/f29a0a7813df398fc494 请注意,这是针对浮点数的,当然,你仍然需要对其进行调整。 我在其他地方使用了一些这样的标准库调用,例如用于长整数 字符串的转换,因此它不是“禁止的”,但我更喜欢没有库调用的解决方案的解决方案.谢谢你的链接 nightcracker,看起来对我很有用。 【参考方案1】:我将在这里做出不可移植的假设:即unsigned long long
比double
具有更准确的数字。 (在我所知道的所有现代桌面系统上都是如此。)
首先,将最高有效整数转换为unsigned long long
。然后将其转换为双 S
。让M
是小于第一步中使用的整数的数量。将S
乘以(1ull << (sizeof(unsigned)*CHAR_BIT*M)
。 (如果移位超过 63 位,则必须将它们拆分为单独的移位并进行一些运算)最后,如果原始数字为负数,则将此结果乘以 -1。
这个四舍五入很多,但即使使用这个四舍五入,由于上述假设,不会丢失任何数字,但在转换为双精度数时无论如何都不会丢失。我认为这与 Mark Ransom 所说的过程类似,但我不确定。
为了从双精度整数转换为大整数,首先使用frexp
将尾数分隔为double M
,将指数分隔为int E
。将M
乘以UNSIGNED_MAX
,并将结果存储在unsigned R
中。如果std::numeric_limits<double>::radix()
为2(我不知道它是否适用于x86/x64),您可以轻松地将R
左移E-(sizeof(unsigned)*CHAR_BIT)
位,然后就完成了。否则结果将改为R*(E**(sizeof(unsigned)*CHAR_BIT))
(其中**
表示幂)
如果性能是一个问题,您可以为您的 bignum 类添加一个重载,以便乘以 std::constant_integer<unsigned, 10>
,它只返回 (LHS<<4)+(LHS<<2)
。如果您愿意,您可以类似地优化其他常量。
【讨论】:
谢谢,这似乎是 Bigint -> Float 方向的一个很好的解决方案,甚至可以通过使用一些#ifdefs 轻松实现便携。我不知道 pow 的效率如何,也许可以通过使用我们创建 2 的幂并以某种方式使用浮点指数的知识来改进它。 好电话,删除pow
以支持位移。快得多。如果位移大于 64 位,我认为双精度值无论如何都不能保持。 (不确定)
哦!我完全没有意识到你也想要 double->bigint,我一想到怎么做就会添加它
好吧,我不知道 x86 double 的基数是否为 2,所以有两种方法。一种简单快速,另一种更难更慢。
对不起,我想我把你和班次搞混了。 2
【参考方案2】:
这篇博文可能对你有所帮助Clarifying and optimizing Integer>>asFloat
否则,您还可以通过这个 SO 问题Converting from unsigned long long to float with round to nearest even 了解算法
【讨论】:
谢谢,他们都为我的实现提供了一些额外的提示。抱歉,我首先忽略了您的帖子。【参考方案3】:您没有明确说明,但我假设您的库仅是整数,而无符号长整数是 32 位和二进制(不是十进制)。 到 double的转换很简单,所以我会先解决这个问题。
从当前棋子的乘数开始;如果数字为正,则为 1.0,如果为负,则为 -1.0。对于 bignum 中的每个无符号长整数,乘以当前乘数并将其添加到结果中,然后将乘数乘以 pow(2.0, 32)
(4294967296.0) 对于 32 位或 pow(2.0, 64)
(18446744073709551616.0) 对于 64 位。
您可以通过仅使用两个最重要的值来优化此过程。即使整数类型中的位数大于双精度,也需要使用 2,因为最高有效值中 used 的位数可能仅为 1。您可以生成乘以 2 的幂到跳过的位数,例如pow(2.0, most_significant_count*sizeof(bit_array[0])*8)
。您不能使用另一个答案中给出的位移,因为它会在第一个值之后溢出。
要从双精度转换,您可以使用frexp
函数将指数和尾数分开。尾数将作为 0.5 和 1.0 之间的浮点值出现,因此您需要将其乘以 pow(2.0, 32) 或 pow(2.0, 64) 以将其转换为整数,然后将指数调整为 -32 或-64 补偿。
【讨论】:
我在我的机器上使用了 uint64_t,但我想保留使用其他东西的选项。您的解决方案的问题是,如果 n 是数字的大小,则需要 O(n) 时间,我认为它应该在恒定时间内可行。 @user1655480,我认为 Mooing Duck 的想法是正确的 - 您应该只需要最重要的 1 或 2 个值来饱和双精度。【参考方案4】:要从大整数变为双精度,只需按照解析数字的方式进行即可。例如,您将数字“531”解析为“1 + (3 * 10) + (5 * 100)”。使用双精度计算每个部分,从最不重要的部分开始。
要从双精度数到大整数,以相同的方式执行,但从最重要的部分开始相反。所以,要转换 531,首先你会看到它大于 100 但小于 1000。你通过除以 100 找到第一个数字。然后减去得到 31 的余数。然后通过除以 10 找到下一个数字。并且以此类推。
当然,您不会使用十位(除非您将大整数存储为数字)。究竟如何将其拆分取决于大整数类的构造方式。例如,如果它使用 64 位单位,那么您将使用 2^64 的幂而不是 10 的幂。
【讨论】:
从大整数到双精度的转换有效,但对于大数来说效率低下。如果数字的大小为 n,则需要 O(n) 次操作,但我认为转换应该在恒定时间内是可行的。对于另一个方向,我真的不明白这对浮动是如何工作的。 不是 O(n),而是 O(log n)。 (1,000 有 4 位您必须处理。10,000 有 5 位您必须处理。)而且很容易证明,不存在比 O(log n) 更快的操作。大整数包含 O(log n) 个字节的数据,每个这样的字节都必须至少以某种方式处理。 如果我像我一样将 n 定义为输入的大小,则为 O(n)。不,你的证明不起作用。仅仅提到必须处理每个字节对于证明是不够的,对于这些证明至关重要的是,您向我展示了如果算法不查看第 i 个字节,则对于第 i 个字节的某些值是不正确的,即结果取决于每个字节。这不是这里的情况。由于 double 的精度有限,只查看几个最重要的字节就足够了,所以恒定时间是可能的,您不必查看其他字节。 我的算法没有什么能阻止你在双->整数情况下提前停止。至于 integer->double 的情况,你必须弄清楚从哪里开始,这有点复杂。 (这是否允许不是 O(log n) 的算法取决于您的大整数实现的细节。) 好吧,没错,我应该更清楚一点,我对 O(n) 运算的担忧只适用于第一个算法。但是按照你描述的方式,你从最不重要的部分开始,这就是为什么我认为你需要 O(n) 操作。以上是关于在 C++ 中转换 Big Integer <-> double的主要内容,如果未能解决你的问题,请参考以下文章
如何在 C++ 中从字节数组(在 BIG-ENDIAN 中)中提取单个字段