两个通用 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】:
直接回答第一个问题:“[为什么]我不应该使用 == 运算符”?答案是因为较早的运算产生了舍入误差,并且通常不可能计算应用于不正确数据的函数的正确结果。如果您有计算值 x
和 y
来模拟精确的数学值 x 和 y,但 x
和 y
受到舍入误差的影响,那么没有x
和y
的函数告诉我们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:你不能将 constness 与 dynamic_cast
或 static_cast
一起投射,所以据说它们都尊重 constness。他们都尊重 access 控件(不可能强制转换为私有基础 [因为只有派生类方法可能会这样做 Derived* -> Base*
和类的方法是这个 friend declaration is in Base ])
【讨论】:
浮点数的比较(几乎)总是具有明显的含义。混淆和错误的根源是被比较的数字,而不是比较。 是的,我已经添加了,谢谢 @StephenCanon 你投反对票了吗?你能解释一下吗? 我不是你的反对者之一,但请记住,反对者也不需要解释(尽管有时会有所帮助)。 谢谢。我认为在没有解释的情况下投反对票是非常糟糕的做法。如果我是 SO 管理员选举的候选人,这将是我的座右铭【参考方案3】:这篇文章Comparing floating point numbers 深入探讨了浮点比较,这篇What Every Programmer Should Know About Floating-Point Arithmetic 也很不错。
关于coercion
和casting
之间的区别,这个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 问题 [重复]的主要内容,如果未能解决你的问题,请参考以下文章
我想在我的通用应用程序中的两个 UIViewControllers 之间传递数据 [重复]