5

在比较两个“实”数是否相等时,为什么不使用 == 运算符,而应该使用什么?

强制和强制转换有什么区别?我的一般假设是强制转换为另一种类型的值,如下所示:

int n = 9;
return double(n)/5;
4

5 回答 5

4

为什么我不应该使用 == 运算符?

因为它可能不起作用。但问题不在于==操作员。问题在于数字本身。一些浮点数没有精确的二进制表示,浮点数学也不精确。例如,简单的值0.2不能用二进制浮点数精确表示,浮点数的精度有限意味着运算顺序的微小变化可能会改变结果。不同的编译器和 CPU 架构以不同的精度存储临时结果,因此结果会因环境的详细信息而异。

如果您进行计算,然后将结果与某个预期值进行比较,那么您将不太可能得到您想要的结果。

换句话说,如果你做一个计算然后做这个比较:

if (result == expectedResult)

那么这个比较不太可能是真的。如果比较为真,那么它可能是不稳定的——输入值、编译器或 CPU 的微小变化可能会改变结果并使比较为假。

因此,比较浮点数取决于上下文。由于即使更改操作顺序也会产生不同的结果,因此了解您希望数字有多“相等”很重要。这称为epsilon

有很多事情需要考虑:

  • 您对正在比较的值中已经存在的错误的容忍度是多少?
  • H0:在哪些情况下,确切值会有所不同
    ,比较报告为真是可以接受的?
  • H1:在哪些情况下精确值相等,比较报告为假是可以接受的?

混淆和错误的根源是被比较的数字本身,而不是比较。事实上==操作符是可靠的——它总是返回正确的答案并接受实际的参数。

Bruce Dawson 的比较浮点数是查看浮点比较的一个很好的起点。
What Every Programmer Should Know About Floating-Point Arithmetic是另一篇非常不错的文章。

经验法则。

以下定义来自Knuth 的计算机编程艺术

bool approximatelyEqual(float a, float b, float epsilon)
{
    return fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}

bool essentiallyEqual(float a, float b, float epsilon)
{
    return fabs(a - b) <= ( (fabs(a) > fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}

bool definitelyGreaterThan(float a, float b, float epsilon)
{
    return (a - b) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}

bool definitelyLessThan(float a, float b, float epsilon)
{
    return (b - a) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}

选择 epsilon 取决于上下文,并确定您希望数字有多相等。


强迫

它是隐式强制转换,因此当您未显式(直接)指定它时会发生这种情况。它是自动的

    double f(int i){ 
    return i;
    } <- coersion int to double

double  d;
long    l;
int     i;

if (d > i)   d = i; // <-coersion
if (i > l)   l = i; // <-coersion
if (d == l)  d *= 2; // <-coersion

铸件

你明确地使用它,你说

static_cast<>()
dynamic_cast<>()
const_cast<>()
reinterpret_cast<>()

其中每一个都有不同的特殊含义,即dynamic_cast适用于多态类型,并且类型安全,因此您可以使用它将Base*指针(或 Base& 引用)Derived*安全地转换为(或 Derived&) - 测试实际对象是否正是您所是期待它。目标dynamic_cast不一定是多态的——这允许特殊类型的传输(将具体对象包装成多态类型,然后稍后再展开为具体对象,[参见 Bjarne Stroustrup C++..., 15.4.1 Dynamic_cast, p. 408])。reinterpret_cast用于指针转换,指针不必指向多态类型(即具有虚函数的类),static_cast不在运行时检查类型 - 所以它不会引入与dynamic_cast它必须检查type_info与正在投射的对象的关联。

然而,只有static_cast可能会从中转换,void*因为它不需要有关所指向内存的其他信息。同样非常重要的是,static_cast失败会导致运行时错误,但dynamic_cast会在引用被强制转换的情况下返回0指针或抛出bad_cast异常。const_cast是不言自明的,并且必需的:您不能使用or来投射constness,因此据说它们都尊重 constness。他们都尊重访问控制(不可能强制转换为私有基[因为只有派生类方法可能会这样做,并且类的方法是这个 {friend 声明在 Base} 中的朋友]])dynamic_caststatic_castDerived* -> Base*

于 2013-05-13T02:04:08.570 回答
4

直接回答第一个问题:“[为什么]我不应该使用 == 运算符”?答案是因为较早的操作产生了舍入误差,并且通常不可能计算应用于不正确数据的函数的正确结果。如果您有计算值x并且y该模型具有精确的数学值xyx,但y受到舍入误差的影响,则没有函数 of xandy可以告诉我们x是否等于y

这意味着在这些情况下无法计算x是否等于y 。不是==操作员有问题。(==实际上是为数不多的浮点运算之一,它的计算总是完全没有错误;它总是返回给定输入的完全正确的结果。)问题是没有函数可以从这个不正确的结果中给出正确的答案输入。(这不仅仅是==. x_ sqrt(1-x*x)_acos(x)1-x*xx可能错误地大于一。)

那么问题就变成了“我们该怎么做?”</p>

“捏造”比较报告的真假会给程序带来新的错误。所以问题是,您的应用程序中可以接受哪些错误?如果比较报告两个数字相等,而它们与精确数学不相等,那是可接受的还是不可接受的?如果比较报告两个数字相等但它们不相等,这是可接受的还是不可接受的?

这些问题的答案因程序而异。一般来说,有很多事情需要考虑:

  • 正在比较的值中可能已经存在多少错误?
  • 在哪些情况下,确切值会有所不同,比较报告为真是可以接受的?
  • 在哪些情况下,确切值相等,比较报告为假是可以接受的?

上述问题的答案取决于每个应用程序,因此对于使用什么来代替==. 相比之下,有些应用程序可能能够使用相对容差,有些应用程序可能能够使用绝对容差,有些可能需要其他东西。鉴于值中的错误,某些应用程序可能找不到任何可接受的比较。在这种情况下,他们需要重新设计计算以减少错误,或寻找其他解决方案。

所以:

  • 请注意任何与容差进行比较的建议,无论是相对的还是绝对的。使用公差是否可以接受取决于您的应用;没有一般规则。可接受的公差取决于您的应用;没有一般规则。
  • 是否有任何可接受的解决方案取决于您计算值中的错误。错误可能太大而无法解决。谨防任何有关公差大小的一般性建议。浮点误差可以从零变化到无穷大,并且取决于环境,因此没有通用的公差。
  • 请注意,从有错误的数据计算的所有函数都会导致错误,而不仅仅是==.
于 2013-05-13T13:25:55.777 回答
2

This article Comparing floating point numbers goes into floating point comparison in depth and this What Every Programmer Should Know About Floating-Point Arithmetic is a good read too.

With respect to the difference between coercion and casting this SO thread What is the difference between casting and coercing? while although not specific to C++ cover the question well. Basically coercion is implicit while casting is explicit.

于 2013-05-13T01:54:55.257 回答
-1

because floating point types are not 'precise'. some values are not even possible to store and errors may accumulate during operations. so you need to decide, what precision you need. do you care if value 12.345678 is different from 12.34567799999? pick decired precision and use it for comparision.

于 2013-05-13T01:59:57.947 回答
-1

不是答案,只是有关原因的一些背景(不适合评论):

浮点数和双精度数在内部存储为二进制。就像二进制点左侧的 1 是 1、2、4、8、16,...二进制点右侧的数字值 1/2、1/4、1/8、1/16, ... 十进制 1.5 是二进制 1.1,十进制 1.25 是二进制 1.01。

但是像十进制的 1.1 这样的数字实际上是二进制的无理数——所以你无法想出任何可以转换回十进制 1.1 的二进制数。这意味着数字计算方式的任何微小变化都会产生略微不同的结果。

如果你想要准确的答案,你可以使用 BigDecimal——它不使用二进制来存储数字,并且每次都会给你准确的答案,所以你可以放心地使用 .equals() 。

于 2013-05-13T07:15:29.373 回答