诸如printf
之类的函数如何从浮点数中提取数字?我了解原则上可以做到这一点。给定一个数字x
,您希望其中的第一个n
位数字,将x
缩放10的幂,以使x
在pow(10, n)
和{{之间1}}。然后将pow(10, n-1)
转换为整数,并取整数。
我尝试了一下,并且奏效了。有点。对于前16个十进制数字,我的答案与x
给出的答案相同,但此后的数字往往有所不同。 printf
如何做到?
答案 0 :(得分:6)
经典的实现是David Gay的dtoa
。确切的细节有些不可思议(请参阅Why does "dtoa.c" contain so much code?),但总的来说,它的工作原理是使用比32位,64位甚至80位浮点数更高的精度进行基本转换。数。为此,它使用了所谓的“ bigints”或任意精度数字,该数字可以容纳尽可能多的数字,以适合您的内存大小。盖伊(Gay)的代码经过修改后已复制到无数其他库中,包括C标准库的通用实现(因此它可能为printf
提供支持),Java,Python,PHP,JavaScript等。
(作为一个补充说明...并非Gay的所有这些dtoa代码副本都保持最新,因此,因为PHP在解析2.2250738585072011e-308时使用了strem的旧版本挂起了)
通常,如果您以“显而易见”且简单的方式进行操作(例如乘以10的幂然后转换为整数),则会损失少量精度,并且某些结果将不准确...但是也许您会得到正确的前14或15位数字。 Gay的dtoa()实现声称所有数字正确无误...但是结果是,代码很难遵循。跳到底部查看strtod本身,您可以看到它以“快速路径”开头,该路径仅使用普通的浮点算法,但随后它检测到该结果是否不正确,并使用更可靠的算法使用bigints在所有情况下(但速度较慢)。
该实现具有以下引文,您可能会发现它很有趣:
* Inspired by "How to Print Floating-Point Numbers Accurately" by * Guy L. Steele, Jr. and Jon L. White [Proc. ACM SIGPLAN '90, pp. 112-126].
该算法通过计算产生给定二进制数的十进制数范围来工作,并且通过使用更多数字,范围会越来越小,直到您获得准确的结果或可以正确舍入到所请求的位数为止
尤其是从2.2版算法开始,
该算法使用精确的有理算法来执行其计算,因此不会损失准确性。为了生成数字,该算法对数字进行缩放,使其形式为0.d 1 d 2 ...,其中d 1 ,d 2 ,...是基数B的数字。通过将缩放数字乘以输出底数B并取整数部分来计算第一位。其余部分用于使用相同方法计算其余数字。
然后,算法可以继续执行,直到获得准确的结果(这是始终可能的,因为浮点数以2为底,而2是10的倍数)或直到它具有所要求的位数。论文继续证明了该算法的正确性。
还要注意,并非printf
的所有实现都基于Gay的dtoa,这只是一个特别常见的实现,已被大量复制。
答案 1 :(得分:2)
有多种方法可以将浮点数转换为十进制数字而不会出错(准确地或舍入到所需的精度)。
一种方法是使用小学所教授的算法。 C提供了使用浮点数的函数,例如frexp
,该浮点数将小数(也称为有效数,通常错误地称为尾数)和指数分开。给定一个浮点数,您可以创建一个大数组来存储十进制数字,然后计算这些数字。浮点数小数部分的每一位代表2的幂,由浮点数的指数确定。因此,您可以简单地将“ 1”放入数字数组中,然后使用小学算法将其乘或除所需的次数。您可以对每个位执行此操作,然后将所有结果相加,总和是等于浮点数的十进制数字。
商业printf
实现将使用更复杂的算法。讨论它们超出了Stack Overflow问答的范围。关于这一点的开创性论文是Correctly Rounded Binary-Decimal and Decimal-Binary Conversions by David M. Gay。 (here似乎有一个副本,但似乎是由第三方托管的;我不确定它的官方性或持久性。网络搜索可能会打开其他来源。)一种将二进制浮点数转换为十进制的算法,其唯一标识该值的位数最短为Printing Floating-Point Numbers: An Always Correct Method by Marc Andrysco, Ranjit Jhala, and Sorin Lerner。
完成操作的一个关键是printf
不仅将使用浮点格式及其操作来完成工作。它将使用某种形式的扩展精度算术,方法是使用更多位数的整数格式处理浮点数的部分,将浮点数分成多个部分并使用多个浮点数进行处理,或使用精度更高的浮点格式。
请注意,问题的第一步是x乘以10的幂,已经有两个舍入误差。首先,并不是所有的10的幂都可以在二进制浮点数中精确表示,因此仅产生这样的10的幂必然会产生一些表示误差。然后,将x
乘以另一个数字通常会产生无法精确表示的数学结果,因此必须将其四舍五入为浮点格式。
答案 2 :(得分:1)
C或C ++标准都没有为此类事情规定某种算法。因此,不可能回答printf
是如何做到的。
如果您想了解printf
实现的示例,可以在这里查看:http://sourceware.org/git/?p=glibc.git;a=blob;f=stdio-common/vfprintf.c和此处:http://sourceware.org/git/?p=glibc.git;a=blob;f=stdio-common/printf_fp.c