当比较两个“真实”数字是否相等时,为什么我不应该使用==运算符,我应该使用什么呢?
coersion和cast之间有什么区别?我的一般假设是,当你强制一个值为另一种类型时,如下所示:
int n = 9;
return double(n)/5;
答案 0 :(得分:4)
为什么我不应该使用==运算符?
因为它可能不起作用。但问题不在于==
运算符。问题是数字本身。一些浮点数没有精确的二进制表示,浮点数不精确。例如,0.2
之类的简单值无法使用二进制浮点数精确表示,浮点数的有限精度意味着操作顺序的微小变化可能会改变结果。不同的编译器和CPU架构以不同的精度存储临时结果,因此结果将根据您的环境细节而有所不同。
如果您进行计算,然后将结果与某些预期值进行比较,那么您很可能无法获得预期的结果。
换句话说,如果你进行计算然后进行比较:
if (result == expectedResult)
那么比较不太可能。如果比较为真,那么它可能不稳定 - 输入值,编译器或CPU的微小变化可能会改变结果并使比较成为假。
因此,比较浮点数取决于上下文。因为即使改变操作顺序也会产生不同的结果,重要的是要知道你想要数字的“相等”程度。这称为 epsilon 。
有许多事情需要考虑:
混淆和错误的根源是被比较的数字本身,而不是比较。实际上==
运算符是可靠的 - 它总是返回正确的答案,并采用实际的参数。
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_cast
或static_cast
投射 constness ,因此据说它们是都尊重常数。他们也都尊重访问控件(不可能强制转换为私有基础[因为只有派生类方法可能会Derived* -> Base*
而且类的方法是这个{朋友声明的朋友基}])
答案 1 :(得分:4)
直接回答第一个问题:“[为什么]我不应该使用==运算符”?答案是因为早期操作产生了舍入错误,并且通常无法计算应用于错误数据的函数的正确结果。如果您计算了值{em} x 和 y 的精确数学值x
和y
,但x
和{{1}已经受到舍入错误的影响,那么y
和x
没有函数告诉我们 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也很好。
关于coercion
和casting
这个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()。