两个通用 CS 问题 [重复]

Posted

技术标签:

【中文标题】两个通用 CS 问题 [重复]【英文标题】:Two General CS Questions [duplicate] 【发布时间】:2013-05-07 00:06:41 【问题描述】:

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

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

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

【问题讨论】:

没有规定不能使用==。有时您确实确实想检查该数字是否完全等于某个值。然而,大多数时候,您想检查它是否在某个容差范围内相等。 相关:***.com/questions/17333/… 您的问题被解读为来自想了解他在做什么的人。这是优秀计算机科学家(或任何科学家)的核心态度。 这应该是两个独立的问题。 "当比较两个“实”数是否相等时,为什么我不应该使用 == 运算符" 那么,为什么 认为你不应该使用相等运算符在比较平等时? 【参考方案1】:

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

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

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

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

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

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

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

所以:

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

【讨论】:

【参考方案2】:

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

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

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

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

if (result == expectedResult)

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

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

有很多事情需要考虑:

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

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

Bruce Dawson 的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* 指针(或Base& 引用)转换为Derived*(或 Derived&)安全 - 测试实际对象是否正是您所期望的。 dynamic_cast 的目标不必是多态的 - 这允许特殊类型的传输(将具体对象包装为多态类型,然后稍后再次展开为具体对象,[参见 Bjarne Stroustrup C++...,15.4.1 Dynamic_cast ,第 408 页])。 reinterpret_cast 用于指针转换,指针不必指向多态类型(即具有虚函数的类),static_cast 不会在运行时检查类型 - 所以它不会引入一点点开销与dynamic_cast 相关,它必须检查与正在投射的对象关联的type_info

但是只有static_cast 可以从void* 转换,因为它不需要有关所指向内存的其他信息。同样非常重要的是 static_cast 失败会导致运行时错误,但 dynamic_cast 将返回 0 指针或抛出 bad_cast 异常以防引用被强制转换。 const_cast 是不言自明的,需要 is:你不能将 constnessdynamic_caststatic_cast 一起投射,所以据说它们都尊重 constness。他们都尊重 access 控件(不可能强制转换为私有基础 [因为只有派生类方法可能会这样做 Derived* -&gt; Base* 和类的方法是这个 friend declaration is in Base ])

【讨论】:

浮点数的比较(几乎)总是具有明显的含义。混淆和错误的根源是被比较的数字,而不是比较。 是的,我已经添加了,谢谢 @StephenCanon 你投反对票了吗?你能解释一下吗? 我不是你的反对者之一,但请记住,反对者也不需要解释(尽管有时会有所帮助)。 谢谢。我认为在没有解释的情况下投反对票是非常糟糕的做法。如果我是 SO 管理员选举的候选人,这将是我的座右铭【参考方案3】:

这篇文章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 是显式的。

【讨论】:

【参考方案4】:

因为浮点类型不是“精确的”。有些值甚至无法存储,并且在操作过程中可能会累积错误。所以你需要决定,你需要什么精度。您是否关心值 12.345678 是否与 12.34567799999 不同?选择确定的精度并将其用于比较。

【讨论】:

【参考方案5】:

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

浮点数和双精度数在内部存储为二进制。就像二进制点左侧的 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()。

【讨论】:

以上是关于两个通用 CS 问题 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

我可以在java中添加两个通用值吗? [重复]

我想在我的通用应用程序中的两个 UIViewControllers 之间传递数据 [重复]

Powershell通用集合列表不显示多个对象[重复]

一个项目中如果有重复代码,如何变成一个标签使其通用?

ASP.NET Core 中的通用存储库,Startup.cs 中的每个表没有单独的 AddScoped 行?

在 Startup.cs 中初始化 Blob 存储