两个一般的CS问题

时间:2013-05-13 01:50:23

标签: c++ floating-point

当比较两个“真实”数字是否相等时,为什么我不应该使用==运算符,我应该使用什么呢?

coersion和cast之间有什么区别?我的一般假设是,当你强制一个值为另一种类型时,如下所示:

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

6 个答案:

答案 0 :(得分:4)

  

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

因为它可能不起作用。但问题不在于==运算符。问题是数字本身。一些浮点数没有精确的二进制表示,浮点数不精确。例如,0.2之类的简单值无法使用二进制浮点数精确表示,浮点数的有限精度意味着操作顺序的微小变化可能会改变结果。不同的编译器和CPU架构以不同的精度存储临时结果,因此结果将根据您的环境细节而有所不同。

如果您进行计算,然后将结果与某些预期值进行比较,那么您很可能无法获得预期的结果。

换句话说,如果你进行计算然后进行比较:

if (result == expectedResult)

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

因此,比较浮点数取决于上下文。因为即使改变操作顺序也会产生不同的结果,重要的是要知道你想要数字的“相等”程度。这称为 epsilon

有许多事情需要考虑:

  • 您对所比较值中已经错误的容忍度是多少?
  • H0:对于确切值不同的情况,是什么 可以接受比较报告是真的吗?
  • H1:对于精确值相等的情况,它是什么 接受比较报告是假的吗?

混淆和错误的根源是被比较的数字本身,而不是比较。实际上==运算符是可靠的 - 它总是返回正确的答案,并采用实际的参数。

在查看浮点比较时,布鲁斯道森的

Comparing floating point numbers是一个很好的起点 What Every Programmer Should Know About Floating-Point Arithmetic是另一篇非常好的文章。

经验法则。

以下定义来自The art of computer programming by 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*指针(或安全地Derived*(或Derived&amp;)的基础和参考) - 测试实际对象是否正是您期望的那样。 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是自解释的,并且 :您无法使用dynamic_caststatic_cast投射 constness ,因此据说它们是都尊重常数。他们也都尊重访问控件(不可能强制转换为私有基础[因为只有派生类方法可能会Derived* -> Base*而且类的方法是这个{朋友声明的朋友基}])

答案 1 :(得分:4)

直接回答第一个问题:“[为什么]我不应该使用==运算符”?答案是因为早期操作产生了舍入错误,并且通常无法计算应用于错误数据的函数的正确结果。如果您计算了值{em} x 和 y 的精确数学值xy,但x和{{1}已经受到舍入错误的影响,那么yx没有函数告诉我们 x 是否等于 y

这意味着在这些情况下计算 x 是否等于 y 不可能y运算符不是问题。 (==实际上是少数几乎没有错误计算的浮点运算之一;它总是返回给定输入的完全正确的结果。)问题是没有函数从这个错误的输入中得出正确答案。 (这不仅仅是==的问题。如果你有==有舍入错误,那么几乎所有用它计算的函数都会包含错误:x会有错误,{ {1}}会有错误。更糟糕的是,他们可能会发出异常信号,因为sqrt(1-x*x)可能是错误的否定或acos(x)可能错误地大于1。)

然后问题变成“我们做了什么呢?”

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

这些问题的答案因计划而异。通常,有很多事情需要考虑:

  • 被比较的值中可能已经存在多少错误?
  • 对于精确值不同的情况,比较报告是否可以接受?
  • 对于精确值相等的情况,比较报告是否可以接受?

上述问题的答案取决于每个应用程序,因此没有关于使用什么而不是1-x*x的一般答案。一些应用程序可能能够使用相对容差进行比较,有些应用程序可能能够使用绝对容差,有些可能需要其他应用程序。鉴于值中的错误,某些应用程序可能找不到任何可接受的比较。在这种情况下,他们需要重新设计计算以减少错误,或找到其他解决方案。

所以:

  • 请注意与容差相比较的任何建议,无论是相对的还是绝对的。是否使用公差取决于您的应用;没有一般规则。可接受多少容差取决于您的应用;没有一般规则。
  • 是否有任何可接受的解决方案取决于计算值中的错误。错误可能太大而无法提供解决方案。请注意有关容差程度的任何一般性建议。浮点误差可以从零到无穷大,并且取决于具体情况,因此没有一般公差可行。
  • 请注意,根据错误数据计算的所有函数会导致错误,而不仅仅是x

答案 2 :(得分:2)

这篇文章Comparing floating point numbers进行了深入的浮点比较,这个What Every Programmer Should Know About Floating-Point Arithmetic也很好。

关于coercioncasting这个SO线程What is the difference between casting and coercing?之间的差异虽然不是特定于C ++,但很好地解决了这个问题。基本上coercion是隐式的,而casting是明确的。

答案 3 :(得分:-1)

因为浮点类型不是“精确”的。某些值甚至无法存储,并且在操作期间可能会累积错误。所以你需要决定你需要什么样的精度。如果值12.345678与12.34567799999不同,你关心吗?选择合适的精度并用它进行比较。

答案 4 :(得分:-1)

除了这里提到的技术考虑因素(这是不使用平等的主要原因)之外,我认为有一个更深层的概念:两个不同的实数永远不会相等。

如果在(a,b)范围内随机选择两个实数(数学上说),它们相等的概率恰好为零。如果我想赶上车辆通过70KPH的时刻(这是while (x != 70.0)的意思),这个时刻的长度恰好为零。因此,即使机器能够以无限精度计算,您仍然不希望在大多数情况下使用相等;实际中的实际数量实际上是身份 - 你不会偶然得到它。

答案 5 :(得分:-1)

不是答案,只是为什么(不适合评论)的背景:

Floats和Doubles在内部存储为二进制。就像二进制点左边的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()。