如何从浮点计算中丢弃不需要的额外精度?

时间:2014-12-22 23:59:37

标签: c++ floating-point

首先,我只是说我知道浮点计算是近似的 - 受到舍入错误的影响 - 所以通常你不能测试完全相等并期望它能够工作。 但是,浮点计算仍然是确定性的 - 使用相同的输入运行相同的代码,你应该得到相同的结果。 [看到编辑结束]我刚刚有一个惊喜,但没有成功。

我正在编写一个实用程序来从Photoshop PSD文件中提取一些信息。包含三次贝塞尔曲线的路径是其中的一部分,我需要计算贝塞尔曲线的轴对齐边界框。对于这个理论,我通过另一个SO问题找到了A Primer on Bezier Curves

方法快速摘要......

  1. 确定三次贝塞尔曲线的导数 - 二次贝塞尔曲线。
  2. 使用二次公式求解每个维的二次方,给出曲线的停止点(包括最大值和最小值)的参数值。
  3. 评估这些参数(以及起点和终点)的三次贝塞尔曲线位置,展开边界框。
  4. 因为我想要实际的边界点以及边界框,所以进行第二次传递,计算位置并拒绝那些不在最终边界框上的点。
  5. 第四步是问题所在。扩展边界框不应更改浮点值 - 它只选择最大/最小值并存储它们。我使用相同的曲线控制点和相同的参数重新计算相同的点,但是比较与边界框的精确相等是否应该通过它。

    添加一些调试输出使它工作。删除调试代码但编译调试模式使其工作。没有内存损坏的可能性。

    我认为存储的值(以边界框的形式)在某种程度上比新重新计算的位置精度低,这似乎是正确的。当我添加调试代码或在调试模式下编译时,某些内联不会发生,更少的优化会发生,因此新计算的值会获得与存储的边界框边界相同的精度损失。

    最后,我使用volatile变量解决了问题。新重新计算的值被写入volatile变量,然后读回,从而迫使编译器将精度降低的存储版本与另一个精度降低的存储版本进行比较。这似乎有效。

    但是,我真的不知道是否应该工作 - 这只是一个想法,我知道这样的敏感事件可能是编译器编写者对标准中技术性的解释。我不确切知道标准的相关保证是什么,我不知道是否有这个问题的传统解决方案。我甚至不确定是什么激励我尝试volatile我从未使用的IIRC,因为我在1996年左右在嵌入式系统上使用纯C工作。

    当然我可以计算一次位置向量,并为边界矩形和过滤存储它们,但我对这个特定问题感到好奇。正如你可能猜到的那样,我没有做太多的浮点工作,所以我对这些问题并不熟悉。

    那么 - volatile的使用是否正确?正如我怀疑的那样,这是不必要的低效率吗?是否有一种惯用的方法来强制额外的精度(超出类型的限制)被丢弃?

    另外,为什么比较精确相等不会强制首先考虑类型的精度?比较精确的相等性和额外的精度(可能是不可靠的)保留了一个有用的事情吗?

    [编辑 - 继Hans Passants首次发表评论后,我更多地考虑了上述内容。显然,由于不同的内联决策,可以不同地优化对相同代码的两个不同调用。这不仅仅是在一个层面 - 它可以在任何深度的内联函数中发生在一段代码中。这意味着精确度降低几乎可以在任何地方发生,这实际上意味着即使相同的源代码与相同的输入一起使用,它也可以提供不同的结果。

    FPU是确定性的,因此可能任何特定目标代码都是确定性的,但对同一函数的两个不同调用可能不会对该函数使用相同的目标代码。现在我感觉有点白痴,没有看到我已经想到的含义 - 哦,好吧。如果在接下来的几天没有更好的答案,我会自己添加一个。]

2 个答案:

答案 0 :(得分:3)

  

添加一些调试输出使它工作。删除调试代码但编译调试模式使其工作。没有内存损坏的可能性。

你似乎遇到了David Monniaux在article“验证浮点计算的陷阱”中详细描述的那种麻烦“:

  

以上示例表明,显然不应改变计算语义的常见调试实践实际上可能会改变计算结果。在计算过程中添加日志记录语句可能会改变寄存器的调度,[...]

请注意,他的大多数指责都是针对C编译器的,并且C编译器的情况 - 自他的文章发表以来已经有所改进:虽然文章是在C99之后编写的,但C编译器所采取的一些自由显然是不允许的。 C99标准或未明确允许或仅在明确定义的框架内允许(有关详细信息,请在C99标准中查找FLT_EVAL_METHODFP_CONTRACT的出现次数。)

C编译器的情况有所改进的一种方法是,即使存在额外的精度,GCC现在也为浮点实现了清晰,确定性,符合标准的语义。相关选项为-fexcess-precision=standard,由-std=c99设置。

WRT问题 - “如何从浮点计算中丢弃不需要的额外精度?” - 简单的答案是“不要”。抛弃一个点的精度是不够的,因为计算中的任何一点都可能丢失或保留额外的精度,因此表面上的再现性是侥幸。

尽管G ++使用与GCC相同的后端,虽然C ++标准遵循math.h(提供FLT_EVAL_METHOD)定义的C标准,但遗憾的是G ++ does not support -fexcess-precision=standard

一种解决方案是将所有浮点计算移动到您将使用-fexcess-precision=standard编译的C文件,并链接到应用程序的其余部分。另一种解决方案是使用-msse2 -mfpmath=sse来使编译器发出没有“过度精度”问题的SSE2指令。后两个选项可以由-m64隐含,因为SSE2早于x86-64指令集,而“AMD64 ABI”已经使用SSE2寄存器传递浮点参数。

答案 1 :(得分:-2)

在您的情况下,与大多数浮点比较的情况一样,应该使用一些容差(有时称为epsilon)来进行相等性测试。如果您想知道一个点是否在一条线上,您不仅要检查它们之间的距离是否为零,还要检查它是否小于某个公差。这将克服你所遇到的“过度精确”问题;您仍然需要注意复杂计算中的累积误差(可能需要更大的容差,或者特定于FP数学的仔细算法设计)。

可能存在特定于平台和编译器的选项以消除过多的FP精度,但依赖于这些选项并不常见,不可移植,并且可能没有其他解决方案那么高效。