我正在阅读Stephen Prata的《C Primer Plus》,它介绍浮点运算的第一种方法是谈论它们在一定程度上的准确性。它特别指出:"C标准规定,浮点必须能够表示至少六个有效数字……浮点必须准确地表示前六个数字,例如33.333333">
这对我来说很奇怪,因为它听起来像是一个浮点运算,精确到六位数,但事实并非如此。1.4存储为1.39999…依此类推。您仍然有错误。
那么,究竟提供了什么呢?一个数字的准确度有界限吗?
在C中,如果不收到编译器警告,就不能在一个浮点中存储超过六个有效数字,但为什么呢?如果你做的数字超过六位数,它似乎也同样准确。
关于下溢和次正规数的章节使这一点更加令人困惑。当你有一个浮点数最小的数字,并将其除以10,你得到的错误似乎不是低于标准的吗?它们似乎只是上面提到的常规舍入误差。
那么,为什么书中说浮点数精确到六位数呢?低于正常值与常规舍入误差有何不同?
假设您有一个有效位数为q的十进制数字:
d q−1 d q−2 d q−3…d0,
并且让我们还将其设为浮点十进制数字,这意味着我们将其按10的幂缩放:
d q−1 d q−2 d q−3…d0•10e。
接下来,我们将此数字转换为float
。许多这样的数字不能在float
中精确表示,因此我们将结果四舍五入到最接近的可表示值。(如果有平局,我们四舍五入使低位偶数。)结果(如果我们没有上溢或下溢)是某个浮点数x。根据浮点数的定义(在C 2018 5.2.4.2.23中),它由某个基数中的一些数字表示,该基数按幂缩放。假设它是基二,x是:
b p−1 b p−2
p−3…b 0•2p。
接下来,我们将此float
x转换回具有q有效数字的十进制。类似地,float
值x可能不能精确地表示为带有q数字的十进制数字,因此我们得到了一些可能的新数字:
n q−1 n q−2
q−3…n 0•10m。
事实证明,对于任何float
格式,都有一些数字q,因此,如果我们开始使用的十进制数字限制为q数字,则此往返转换的结果将等于原始数字。当四舍五入到float
,然后返回到q十进制数字时,q数字中的每个十进制数字都会产生起始数字。
在2018年C标准第5.2.4.2.2条第12段中,告诉我们,该数字q必须至少为6(C实现可能支持更大的值),并且C实现应为其定义一个称为FLT_DIG
的预处理器符号(在float.h
中)。
因此,考虑到您的示例数字1.4,当我们将其转换为IEEE-754基本32位二进制格式的float
时,我们正好得到1.39999997615814208984375(这是它的数学值,为了方便起见,以十进制显示;对象中的实际位以二进制表示)。当我们将其完全精确地转换为十进制时,我们得到"1.39999997615814208984375"。但如果我们将其转换为十进制并四舍五入六位数,我们得到了"1.40000"。因此1.4在往返过程中幸存下来。
换句话说,不是真的通常情况下,float
中可以表示六个十进制数字而不会发生变化,但float
确实携带了足够的信息,您可以从中恢复六个十进制位数
当然,一旦你开始做算术,错误通常会复合,你不能再依赖六位小数了。
感谢Govind Parmar引用C11(或C99)的在线示例。
您所指的"6"是"FLT_DECIMAL_DIG"。
http://c0x.coding-guidelines.com/5.2.4.2.2.html
十进制数字的数目,n,使得具有p基数b数字可以四舍五入为带n的浮点数十进制数字并返回,不更改值,
{ p log10 b if b is a power of 10 { { [^1 + p log10 b^] otherwise
FLT_DECIMAL_DIG 6
DBL_DECIMAL_DIAG 10 LDBL_DECIMAL_DIG10
"亚正常"的意思是:
什么是次正规浮点数?
当指数位为零且尾数为零时,数字是次正规的为非零。它们是介于零和最小法线之间的数字数字它们的尾数中没有隐含的前导1。
强烈建议:
如果你不熟悉"浮点运算"(或者,坦率地说,即使你不熟悉),这是一篇很好的阅读(或评论)文章:
每个程序员应该知道的浮点算术