一尘不染

如何打印浮点数的EXACT值?

algorithm

首先,这不是浮点新手问题。我知道浮点算术(更不用说超越函数)的结果通常不能精确表示,并且大多数终止小数都不能精确表示为二进制浮点数。

就是说,每个可能的浮点值都精确对应于一个有向有理数(一个有理数p/q,其中q是2的幂),而后者又有一个精确的小数表示。

我的问题是:您如何有效地找到这种精确的十进制表示形式?sprintf通常只指定最多个有效数字来表示相似功能,以唯一地确定原始浮点值;他们不一定会打印出精确的十进制表示形式。我知道我使用过一种算法,但是它很慢,指数O(e^2)在哪里e。概述如下:

  1. 将尾数转换为十进制整数。您可以通过将位分开以直接读取尾数来执行此操作,也可以编写一个凌乱的浮点循环,该循环首先将值乘以2的幂以将其置于1 <= x <10的范围内,然后进行拉通过将值强制转换为int,减并乘以10来一次删除一个数字。
  2. 通过重复乘以或除以2来应用指数。这是对生成的十进制数字 字符串 的运算。每〜3个乘法将在左侧添加一个额外的数字。每个分割将在右侧添加一个额外的数字。

这真的是最好的办法吗?我对此表示怀疑,但我不是浮点专家,我无法找到一种方法来对数字的浮点表示进行以10为底的计算,而又不会出现结果不准确的可能性(乘以或除以除2的幂以外的任何值都是对浮点数的有损运算,除非您知道可以使用自由位)。


阅读 272

收藏
2020-07-28

共1个答案

一尘不染

这个问题有一个官僚部分和一个算法部分。浮点数在内部存储为(2 e
×m),其中e是指数(本身为二进制),m是尾数。问题的官僚部分是如何访问此数据,但R.似乎对该问题的算法部分更感兴趣,即将(2 e
×m)转换为十进制形式的分数(a / b)。用几种语言来解决官僚主义问题的答案是“ frexp”(这是我今天之前不知道的一个有趣的细节)。

的确,乍看之下,只需要用十进制写2 e就需要O(e 2)的工作,而尾数还有更多的时间。但是,得益于Schonhage-
Strassen
快速乘法算法的神奇之处,您可以在e(e)时间内完成操作,其中波浪号表示“最多对数因子”。如果您将Schonhage-
Strassen视为魔术,那么思考该怎么做并不难。如果e是偶数,则可以递归计算2 e /
2,然后使用快速乘法对其求平方。另一方面,如果e为奇数,则可以递归计算2 e-1,然后将其加倍。您必须小心检查以10为基础的版本是否有Schonhage-
Strassen版本。尽管没有广泛记录,但是可以在任何基础上完成。

将很长的尾数从二进制转换为以10为基数不是完全相同的问题,但是它有相似的答案。您可以将尾数分为两半,m = a 2 k +
b。然后将a和b递归转换为以10为底,将2 ^ k转换为以10为底,然后进行另一个快速乘法,以10为底计算m。

所有这些背后的抽象结果是,您可以在Õ(N)时间内将整数从一个基数转换为另一个基数。

如果问题是关于标准的64位浮点数,那么对于花哨的Schonhage-
Strassen算法而言,它太小了。在此范围内,您可以使用各种技巧来节省工作。一种方法是将所有2 e的
2048个值存储在查找表中,然后使用不对称乘法(在长乘法和短乘法之间)处理尾数。另一个技巧是在10000(或更高的幂,取决于体系结构)的基础上工作,而不是10。但是,正如R.在注释中指出的那样,128位浮点数已经允许足够大的指数调用询问查找表和标准长乘法。实际上,长乘法是最快的几位数字,然后在相当大的范围内可以使用Karatsuba乘法Toom-
Cook乘法
,然后对Schonhage-
Strassen进行变形,不仅在理论上而且在实践中都是最佳的。

实际上,大整数包GMP已经具有Õ(N)个时间基数转换,以及选择乘法算法的良好启发法。他们的解决方案与我的解决方案之间的唯一区别是,他们无需在基数10中进行任何大的算术运算,而是在基数2中计算出10的大幂。几种方式。

2020-07-28