首先,这不是浮点新手问题 . 我知道浮点运算的结果(更不用说超越函数)通常不能准确表示,并且大多数终止小数不能完全表示为二进制浮点数 .
也就是说,每个可能的浮点值完全对应于二元有理数(有理数 p/q
,其中 q
是2的幂),而后者又具有精确的十进制表示 .
我的问题是:你如何有效地找到这个精确的十进制表示? sprintf
和类似的函数通常只指定多个有效数字来唯一确定原始浮点值;他们没有使用,但它很慢, O(e^2)
其中 e
是指数 . 这是一个大纲:
-
将尾数转换为十进制整数 . 你可以通过拉开这些位来直接读取尾数,或者你可以编写一个凌乱的浮点循环,首先将该值乘以2的幂,使其在1 <= x <10的范围内,然后拉通过转换为int,减去并乘以10,一次关闭一个数字 .
-
通过重复乘以或除以2来应用指数 . 这是对您生成的 string 十进制数字的运算 . 每次~3次乘法将在左侧添加一个额外的数字 . 每个单独的dividion将在右侧添加一个额外的数字 .
这真的是最好的吗?我对此表示怀疑,但我不是浮点专家,我无法找到一种方法对数字的浮点表示进行基数10计算,而不会遇到不精确结果的可能性(乘以或除以除了你知道你有空闲位之外,除了2的幂之外的任何东西都是浮点数的有损操作 .
10 回答
你没有 . 你最接近的是转储字节 .
您可以要求比默认值更多的有效数字:
打印
0.1000000000000000055511151231257827021181583404541015625
.如果您想要更精确的结果,为什么不使用定点数学呢?转换很快 . 错误是已知的,可以解决 . 不是你问题的确切答案,而是你的不同想法 .
在我的头脑中,为什么不首先将指数分解为二进制指数的总和,然后你的所有操作都是无损的 .
即
然后总结:
我打算将其分解为位数上的O(n),移位为O(1),求和为O(n)位...
你必须有一个足够大的整数类来存储结果,当然......
让我知道 - 我对此感到好奇,这真让我思考 . :-)
这个问题有一个官僚部分和一个算法部分 . 浮点数在内部存储为(2e×m),其中e是指数(本身是二进制),m是尾数 . 问题的官僚部分是如何访问这些数据,但R.似乎对问题的算法部分更感兴趣,即将(2e×m)转换为十进制形式的分数(a / b) . 几种语言的官僚问题的答案是"frexp"(这是我今天之前不知道的一个有趣的细节) .
确实,乍一看,O(e2)工作只需要用十进制写2e,并且尾数的时间更长 . 但是,由于Schonhage-Strassen快速乘法算法的神奇之处,你可以在Õ(e)时间内完成它,其中波浪线意味着"up to log factors" . 如果你认为Schonhage-Strassen是神奇的,那么想想该做什么并不难 . 如果e是偶数,则可以递归计算2e / 2,然后使用快速乘法对其进行平方 . 另一方面,如果e是奇数,你可以递归计算2e-1然后加倍 . 您必须小心检查基础10中是否存在Schonhage-Strassen的版本 . 虽然没有广泛记录,但可以在任何基础上完成 .
将非常长的尾数从二进制转换为基数10并不是完全相同的问题,但它有类似的答案 . 您可以将尾数分成两半,m = a 2k b . 然后递归地将a和b转换为基数10,将2 ^ k转换为基数10,并进行另一次快速乘法以计算基数10中的m .
所有这些背后的抽象结果是你可以在Õ(N)时间内将整数从一个基数转换为另一个基数 .
如果问题是关于标准的64位浮点数,那么它对于花哨的Schonhage-Strassen算法来说太小了 . 在此范围内,您可以使用各种技巧来保存工作 . 一种方法是将2e的所有2048个值存储在查找表中,然后在尾数中使用不对称乘法(在长乘法和短乘法之间) . 另一个技巧是在基数10000(或10的更高功率,取决于架构)而不是基数10工作 . 但是,正如R.在评论中指出的那样,128位浮点数已经允许足够大的指数调用询问查找表和标准长乘法 . 实际上,长乘法是最快到几个数字,然后在一个重要的中等范围内可以使用Karatsuba multiplication或Toom-Cook multiplication,然后Schonhage-Strassen的变化最好不仅在理论上而且在实践中 .
其实,大整数包GMP已经有Õ(N)时基数转换,以及选择乘法算法的良好启发式 . 他们的解决方案和我的解决方案之间的唯一区别是,它不是在基数10中进行任何大算术,而是在基数2中计算10的大功率 . 在这个解决方案中,它们还需要快速除法,但这可以从任何快速乘法中获得 . 几种方式 .
我已经看到你已经接受了一个答案,但是这里有一些你可能想要看到的转换的开源实现:
David Gay的
dtoa()
函数在dtoa.c
(http://www.netlib.org/fp/dtoa.c) .glibc文件
/stdio-common/printf_fp.c
中的函数___printf_fp()
(例如http://ftp.gnu.org/gnu/glibc/glibc-2.11.2.tar.gz) .两者都会打印
%f
类型printf
中所要求的数字(正如我在这里所写的那样:http://www.exploringbinary.com/print-precision-of-dyadic-fractions-varies-by-language/和http://www.exploringbinary.com/print-precision-of-floating-point-integers-varies-too/) .打印浮点数方面已经做了很多工作 . 黄金标准是打印出最小长度的十进制等效值,这样当读回十进制等效值时,无论回读期间的舍入模式如何,都会得到相同的浮点数 . 您可以在优秀的paper by Burger and Dybvig中阅读有关该算法的信息 .
虽然它是C#并且您的问题用C标记,但Jon Skeet有代码将
double
转换为其精确表示形式的字符串:http://www.yoda.arachsys.com/csharp/DoubleConverter.cs从快速浏览一下,移植到C似乎并不太难,甚至更容易用C语言编写 .
经过进一步的反思,似乎Jon的算法也是O(e ^ 2),因为它也在指数上循环 . 但是,这意味着算法是O(log(n)^ 2)(其中n是浮点数),我不确定你可以在比对数平方时间更好的基础上从基数2转换到基数10 .
我自己也不是浮点专家,我会推迟使用经过良好测试的开源库 .
GNU MPFR是一个很好的 .
有3种方式
这是最精确的方式 . 我更喜欢
hex
,因为它更像是基础10
用于阅读/感觉像F.8h = 15.5
这里没有精度损失 .有了这个,我的意思是只有数字
1
在您的数字中尽可能接近 .num
integer digits 简单而精确(无精度损失):num
的 fractional digits 更加棘手,凭经验我发现了这个:如果您创建一个依赖表
n02 <-> n10
,那么您会看到常量0.30102999566398119521373889472449
仍然存在,但是从8位开始,因为less不能表示具有令人满意的精度的0.1
(0.85 - 1.15
) . 因为基数2
的负指数,依赖关系不是线性的,而是模式 . 此代码适用于较小的位计数(<=52
)但在较大的位计数时可能会出错,因为使用的模式不完全适合log10(2)
或1/log2(10)
.对于更大的位数,我使用这个:
但那个公式是32位对齐!!!还有更大的位数广告错误
P.S. 进一步分析十进制数的二进制表示
可以揭示确切的模式重复,这将导致确切的相关数量任何位数的数字 .
为清楚起见:
最后不要忘记绕过数字!这意味着如果最后一个相关数字之后的数字是十进制的
>=5
而不是最后一个相关的数字应该是+1
...并且如果它已经是9
,那么你必须转到前一个数字,依此类推......要打印 fractional binary number 的精确值,只需打印小数
n
位数,其中n
是小数位数,因为表示的值是此值的总和,因此 fractional decimals 的数量不能大于 LSB 的小数位num
. 上面的内容(项目符号 #2 )与将十进制数存储到float
(或仅打印相关的小数)相关 .两个确切值的负权力......
现在
10
的负功率由64位doubles
的精确值样式打印:现在负数为10的相关十进制数字仅打印样式(我更习惯于此)为64位
doubles
:希望能帮助到你 :)