gez*_*eza 46 c++ floating-point precision language-lawyer floating-point-comparison
众所周知,在比较浮点值时必须小心.通常,==我们使用一些基于epsilon或ULP的相等测试来代替使用.
但是,我想知道,有什么案例,使用时==完全没问题吗?
看看这个简单的片段,哪些案例可以保证成功?
void fn(float a, float b) {
float l1 = a/b;
float l2 = a/b;
if (l1==l1) { } // case a)
if (l1==l2) { } // case b)
if (l1==a/b) { } // case c)
if (l1==5.0f/3.0f) { } // case d)
}
int main() {
fn(5.0f, 3.0f);
}
Run Code Online (Sandbox Code Playgroud)
注意:我已经检查了这个和这个,但它们不包括(全部)我的情况.
注2:似乎我必须添加一些加号信息,所以答案在实践中很有用:我想知道:
这是我在目前的标准草案中找到的唯一相关陈述:
浮点类型的值表示是实现定义的.[注意:本文档对浮点运算的准确性没有要求; 另见[support.limits]. - 结束说明]
那么,这是否意味着甚至"案例a)"是实现定义的?我的意思是,l1==l1绝对是一个浮点运算.那么,如果一个实现是"不准确的",那么可能l1==l1是假的?
我认为这个问题不是重复的浮点数==永远好吗?.这个问题没有解决我提出的任何案件.同一主题,不同的问题.我想特别针对案例a)-d)得到答案,因为我无法在重复的问题中找到答案.
小智 15
但是,我想知道,有什么情况,当使用==完全没问题?
当然有.一类示例是不涉及计算的用法,例如只应在更改时执行的setter:
void setRange(float min, float max)
{
if(min == m_fMin && max == m_fMax)
return;
m_fMin = min;
m_fMax = max;
// Do something with min and/or max
emit rangeChanged(min, max);
}
Run Code Online (Sandbox Code Playgroud)
另请参见浮点数==是否正常?并且是浮点==过好不好?.
被控案件可能"有效".实际案例可能仍然失败.另外一个问题是,通常优化会导致计算方式的微小变化,因此符号上结果应该相等,但数字上它们是不同的.理论上,上述例子在这种情况下可能会失败.一些编译器提供了以性能为代价产生更一致结果的选项.我建议"总是"避免浮点数相等.
物理测量的平等以及数字存储的浮动通常是没有意义的.因此,如果您的代码中的浮点数相等,那么您可能做错了.您通常希望大于或小于或小于公差范围内.通常可以重写代码,以避免这些类型的问题.
保证只有a)和b)成功完成任何理智的工作(有关详细信息,请参见下面的法律条款),因为它们比较以相同方式得出并精确到四舍五入的两个值float。因此,保证两个比较值都与最后一位相同。
情况c)和d)可能会失败,因为计算和后续比较可能比进行更高的精度float。不同的舍入double应该足以使测试失败。
请注意,如果涉及无限式或NAN,则情况a)和b)可能仍会失败。
使用该标准的N3242 C ++ 11工作草案,我发现了以下内容:
在描述赋值表达式的文本中,明确指出类型转换发生在[expr.ass] 3:
如果左操作数不是类类型,则将表达式隐式转换为左操作数的cv不合格类型(第4条)。
第4章涉及标准转换[conv],其中包含以下有关浮点转换的内容[conv.double] 1:
浮点类型的prvalue可以转换为另一种浮点类型的prvalue。如果可以在目标类型中准确表示源值,则转换的结果就是该准确表示。如果源值在两个相邻的目标值之间,则转换的结果是这些值之一的实现定义选择。否则,行为是不确定的。
(强调我的。)
因此,我们可以保证实际上定义了转换结果,除非我们要处理的值超出可表示的范围(例如float a = 1e300,即UB)。
当人们想到“内部浮点表示可能比代码中显示的更为精确”时,他们会想到标准[expr] 11中的以下句子:
浮点操作数的值和浮点表达式的结果可以比类型所需的精度和范围大。类型不会因此改变。
请注意,这适用于操作数和结果,不适用于变量。脚注60强调了这一点:
强制转换和赋值运算符仍然必须按照5.4、5.2.9和5.17中所述执行其特定的转换。
(我想,这是Maciej Piechotka在注释中要注明的脚注-编号似乎已经在他使用的标准版本中发生了变化。)
因此,当我说时float a = some_double_expression;,我保证表达式的结果实际上会舍入为可由a表示float(仅当值超出界限时才调用UB),然后a再引用该舍入后的值。
一个实现确实可以指定舍入的结果是随机的,从而破坏情况a)和b)。但是,Sane实现不会这样做。
| 归档时间: |
|
| 查看次数: |
3391 次 |
| 最近记录: |