是否存在处理浮点不准确的一般最佳实践策略?
我正在研究的项目试图通过将所有内容包装在一个包含浮点值并重载运算符的Unit类中来解决它们。如果数字“足够接近”,那么数字被认为是相等的,比如>或者<通过比较略低或更高的值来完成。
我理解封装处理此类浮点错误的逻辑的愿望。但鉴于这个项目有两个不同的实现(一个基于被比较的数字的比率和一个基于绝对差异的比率)并且我被要求查看代码,因为它没有做正确,策略似乎是一个坏人。
那么尝试确保处理程序中所有浮点不准确的策略最好是什么?
答案 0 :(得分:2)
答案 1 :(得分:2)
通常,您希望尽可能使数据保持愚蠢。行为和数据是应该分开的两个问题。
在我看来,最好的办法就是不要有单元课。如果你必须拥有它们,那么除非必须始终以一种方式工作,否则应避免重载运算符。通常它不会,即使你认为它。正如评论中所提到的,例如它打破了严格的弱排序。
我认为处理它的理智方法是创建一些与其他任何东西无关的具体比较器。
struct RatioCompare {
bool operator()(float lhs, float rhs) const;
};
struct EpsilonCompare {
bool operator()(float lhs, float rhs) const;
};
编写算法的人可以在他们的容器或算法中使用这些算法。这允许代码重用而无需要求任何人都使用特定策略。
std::sort(prices.begin(), prices.end(), EpsilonCompare());
std::sort(prices.begin(), prices.end(), RatioCompare());
通常人们试图超载运算符以避免这些事情会引起关于“良好默认值”等的抱怨。如果编译器立即告诉您没有默认值,则很容易修复。如果客户在百万行价格计算中告诉您某些事情不对,那么追踪就更难了。如果有人在某个时候更改了默认行为,这可能会特别危险。
答案 2 :(得分:1)
这两种技术都不好。请参阅this文章。
Google Test是一个在各种平台上编写C ++测试的框架。
gtest.h 包含 AlmostEquals 函数。
// Returns true iff this number is at most kMaxUlps ULP's away from
// rhs. In particular, this function:
//
// - returns false if either number is (or both are) NAN.
// - treats really large numbers as almost equal to infinity.
// - thinks +0.0 and -0.0 are 0 DLP's apart.
bool AlmostEquals(const FloatingPoint& rhs) const {
// The IEEE standard says that any comparison operation involving
// a NAN must return false.
if (is_nan() || rhs.is_nan()) return false;
return DistanceBetweenSignAndMagnitudeNumbers(u_.bits_, rhs.u_.bits_)
<= kMaxUlps;
}
Google实施良好,快速且与平台无关。
小文档是here。
答案 3 :(得分:1)
对我来说,浮点错误本质上是那些在x86上会导致浮点异常的错误(假设协处理器启用了该中断)。一个特殊情况是“不精确”异常,当结果不能以浮点格式精确表示时(例如将1除以3时)。在浮点世界中还不在家的新手会期待确切的结果,并会认为这个案例是错误的。
我认为有几种策略可供选择。
仅举几例。
二十多年前,当我使用带有80287协处理器的80286编写专有数据库引擎时,我选择了一种形式的后期数据检查和使用x87原始操作。由于浮点运算相对较慢,我希望每次加载一个值时都避免进行浮点比较(其中一些会导致异常)。为了实现这一点,我的浮点(双精度)值是带有无符号整数的联合,这样我就可以在调用x87操作之前使用x86操作测试浮点值。这很麻烦,但整数操作很快,当浮点运算开始生效时,有问题的浮点值将在缓存中准备好。
典型的C序列(两个矩阵的浮点除法)看起来像这样:
// calculate source and destination pointers
type1=npx_load(src1pointer);
if (type1!=UNKNOWN) /* x87 stack contains negative, zero or positive value */
{
type2=npx_load(src2pointer);
if (!(type2==POSITIVE_NOT_0 || type2==NEGATIVE))
{
if (type2==ZERO) npx_pop();
npx_pop(); /* remove src1 value from stack since there won't be a division */
type1=UNKNOWN;
}
else npx_divide();
}
if (type1==UNKNOWN) npx_load_0(); /* x86 stack is empty so load zero */
npx_store(dstpointer); /* store either zero (from prev statement) or quotient as result */
npx_load会将值加载到x87堆栈的顶部,前提是它是有效的。否则堆栈顶部将为空。 npx_pop只删除当前位于x87顶部的值。 BTW“npx”是“Numeric Processor eXtenstion”的缩写,因为它有时被称为。
选择的方法是我处理浮点问题的方法,这些问题源于我在试图使协处理器解决方案在应用程序中以可预测的方式运行时遇到的令人沮丧的经历。
可以肯定的是,这个解决方案导致了开销但纯粹的
*dstpointer = *src1pointer / *src2pointer;
是不可能的,因为它不包含任何错误处理。这种错误处理的额外成本超过了如何准备指向值的指针。此外,99%的情况(两个值都有效)非常快,所以如果对其他情况的额外处理速度较慢,那么呢?