不精确浮点常量的警告

Posted

技术标签:

【中文标题】不精确浮点常量的警告【英文标题】:Warning for inexact floating-point constants 【发布时间】:2012-09-27 10:32:47 【问题描述】:

诸如“为什么不是 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1 = 0.8?”这样的问题让我想到......

...让编译器警告浮点常量可能会很好,否则他们需要无限的空间来存储无限的数字)。

我查看了 gcc 警告,但到目前为止没有发现任何用于此目的的警告(-Wall-Wextra-Wfloat-equal-Wconversion-Wcoercion(不支持或仅 C?)、-Wtraditional(仅限 C)似乎没有做我想做的事)。

我在 Microsoft Visual C++ 编译器中也没有发现这样的警告。

我是否缺少隐藏或很少使用的选项?

是否有任何编译器有这种警告?

编辑:此警告可用于教育目的,并提醒那些刚接触浮点的人。

【问题讨论】:

不,因为编译器几乎会对任何严重的数字代码发疯。大多数以 10 为底的浮点数都不能精确表示(包括非常常见的东西,比如 π) @James:或者更具体地说,如果您需要知道,请不要使用浮点数。如果你不知道也不在乎,你很好:-) 您可能会喜欢这篇关于最近向 C 静态分析器添加警告的博文:blog.frama-c.com/index.php?post/2011/11/19/… @nneonneo:libm 源(或者,至少是好的数学库源)不包含“幻数”。它们包含由专家精心制作的数字,并且遵循技术和工程规则,而不是魔法。在设计数字时,可以很容易地用精确的数字(如十六进制浮点数)来表示;数学库源是一个应该使用精确值并且这个警告会很有用的地方,而不是一个应该忽略并正常接受 slop 的地方。 @stefan 该警告不会警告常量本身,这正是我想要的。 【参考方案1】:

编译器无法发出此类警告没有技术原因。然而,它们只对学生有用(他们应该在开始认真工作之前学习浮点算术是如何工作的)和对浮点做非常好的工作的人。不幸的是,大多数浮点工作都很粗糙。人们将数字扔给计算机,而不考虑计算机的工作原理,并且他们接受所得到的任何结果。

警告必须默认关闭以支持大量现有浮点代码。如果它可用,我会在 Mac OS X 数学库中为我的代码打开它。当然,库中的某些点我们依赖于浮点值的每一位,例如我们使用扩展精度算术的地方,并且值通过多个浮点对象表示(例如,我们将有一个一个对象的高位为 1/π,另一个对象的 1/π 减去第一个对象,第三个对象的 1/π 减去前两个对象,得到大约 150 位的 1/π)。一些此类值在源文本中以十六进制浮点数表示,以避免编译器转换十进制数字时出现任何问题,并且我们可以轻松转换任何剩余的数字以避免新的编译器警告。

但是,我怀疑我们能否说服编译器开发人员相信足够多的人会使用此警告,或者它会捕获足够多的错误以使其值得他们花时间。考虑 libm 的情况。假设我们通常为所有常数写精确的数字,但有一次,写了一些其他数字。这个警告会捕获一个错误吗?那么,有什么错误?最有可能的是,无论如何,数字都会被转换为我们想要的值。在打开此警告的情况下编写代码时,我们可能会考虑如何执行浮点计算,并且我们编写的值是适合我们目的的值。例如,它可能是我们计算的某个极小极大多项式的系数,无论是近似以十进制表示还是转换为某些可精确表示的十六进制浮点数,该系数都尽可能好。

因此,此警告很少会发现错误。也许它会捕捉到我们打错数字的情况,不小心在十六进制浮点数字中插入了一个额外的数字,导致它超出了可表示的有效数字。但这很少见。在大多数情况下,我们使用的数字要么简单而简短,要么是从计算它们的软件中复制和粘贴的。在某些情况下,我们会手动键入特殊值,例如 0x1.fffffffffffffp0。当一个额外的“f”滑入该数字时发出警告可能会在编译期间发现一个错误,但该错误几乎肯定会在测试中快速发现,因为它会极大地改变特殊值。

因此,这样的编译器警告几乎没有用处:很少有人会使用它,而且它会为使用它的人捕获很少的错误。

【讨论】:

好的,这似乎是我能得到的最好的了。 您如何看待使用后缀来指示浮点常数是否应该代表一个精确的数量,是否应该代表一个精确到指定精度的数量,或者是期望表示最接近指定的更精确值的可表示值?在第一个标准下,999999.125 是可接受的float,但 999999.1、999999.12 和 999999.13 不是。根据第二个标准,后三个将被接受,但 999999.10 或 999999.14 不会。这看起来有帮助吗? @supercat:我很好。您必须说服编译器和语言开发人员。【参考方案2】:

警告在源代码中:当您编写 floatdoublelong double 时,包括它们各自的任何文字。显然,一些文字是精确的,但即使这样也无济于事:两个精确值的总和可能不精确,例如,如果它们具有相当不同的比例。让编译器警告不精确的浮点常量会产生错误的安全感。另外,您打算对舍入常量做什么?明确地写出最接近的值会容易出错并混淆意图。以不同的方式编写它们,例如,编写 1.0 / 10.0 而不是 0.1 也会混淆意图并可能产生不同的值。

【讨论】:

这不能回答所述问题。 @AlexeyFrunze 我认为这是您能给出的最佳答案。警告就在那里,在您的源代码中,由于给出的原因,没有编译器供应商会实现另一个。 @JamesKanze 让我们同意不同意。【参考方案3】:

不会有这样的编译器开关,原因很明显。 我们正在用十进制写下二进制组件:

第一个小数位是 0.5

第二个小数位是 0.25

第三个小数位是 0.125

....

你看到了吗?由于数字 5 的奇数结尾 每个 位需要 另一个小数来准确表示它。一位需要一位小数,两位 需要两位小数,以此类推。

所以对于小数浮点数,这意味着对于大多数十进制数 对于单精度浮点数,您需要 24(!)个十进制数字和 53(!!) 双精度十进制数字。 更糟糕的是,exact 数字不携带任何额外信息,它们是纯人工制品 基数变化引起的。

没有人会写下 3.141592653589793115997963468544185161590576171875 为 pi 避免编译器警告。

【讨论】:

我非常清楚这些东西是如何工作的。无论如何,你的答案是什么?迄今为止最接近的是 Pascal Cuoq 的评论。顺便说一句,编译器可以在警告中包含舍入值(如 pi)。 明确的问题:你想用所需的十进制数字写下每个小数(例如帕斯卡 0.1000000000000000055511151231257827021181583404541015625)吗? 我已经说过标准编译器中不会有这样的编译器开关。如果您对我的回答不满意,Eric Postpischil 会详细解释这一点。我解释了原因。【参考方案4】:

我不知道编译器是如何知道的,也不知道编译器会警告您类似的事情。一个数字可以用本质上不准确的东西来精确表示,这只是一个巧合。

【讨论】:

技术上它可以,因为它知道你用来获取浮点值的声明(以及某些浮点数,如小整数和 2 的幂的倍数) , 可以精确表示)。 This code 完成了转换和检查。编译器当然可以做到。 编译器很容易发出警告,因为它知道浮点格式,并且知道将文字转换为的确切值。十进制数在机器浮点中是否具有精确表示并不是“巧合”;这是完全确定的。 (特别是,有很大范围的整数具有精确的表示。)最后,每个机器浮点值都代表一个精确的数字。 (当然,不一定是您想要的数字,但这并不意味着它不准确。) 也许我对巧合这个词的使用不正确,但它背后的感觉仍然是一样的。此外,如果您担心数字是否可以精确表示,那么您可能不应该使用浮点数。

以上是关于不精确浮点常量的警告的主要内容,如果未能解决你的问题,请参考以下文章

如何摆脱 GCC 中“从字符串常量到‘char*’的不推荐转换”警告?

警告:不推荐使用常量 ::Fixnum 生成新模型时

C++ 警告:不推荐使用从字符串常量到“char*”的转换 [-Wwrite-strings]

请帮助:[警告]不推荐将字符串常量转换为 'char*' [-Wwrite-strings]

解决“浮点上下文中的整数除法”警告

PHP 类常量与 php 函数常量给出警告