【问题标题】:Detecting and adjusting for negative zero检测和调整负零
【发布时间】:2012-11-25 21:45:02
【问题描述】:

我有一些从 Java 移植到 C++ 的代码

// since this point is a vector from (0,0,0), we can just take the
// dot product and compare
double r = point.dot(normal);
return (r>=0.0);

但在 C++ 中,r 可以是 +0.0-0.0,当 r 等于 -0.0 时,检查失败。

我尝试在下面的代码中调整负零,但它从未到达 DEBUG("Negative zero") 行。但是r2 确实打印出等于+0.0

// since this point is a vector from (0,0,0), we can just take the
// dot product and compare
double r = point.dot(normal);
if (std::signbit(r)){
    double r2 = r*-1;
    DEBUG("r=%f r=%f", r,r2);
    if (r2==0.0) {
        DEBUG("Negative zero");
        r = 0.0; //Handle negative zero
    }
}
return (r>=0.0);

有什么建议吗?

测试代码:

DEBUG("point=%s", point.toString().c_str());
DEBUG("normal=%s", normal->toString().c_str());
double r = point.dot(normal);
DEBUG("r=%f", r);
bool b = (r>=0.0);
DEBUG("b=%u", b);

测试结果:

DEBUG - point=Vector3D[ x=1,y=0,z=0 ]
DEBUG - normal=Vector3D[ x=0,y=-0.0348995,z=0.0348782 ]
DEBUG - r=0.000000
DEBUG - b=1
DEBUG - point=Vector3D[ x=1,y=0,z=0 ]
DEBUG - normal=Vector3D[ x=-2.78269e-07,y=0.0174577,z=-0.0174391 ]
DEBUG - r=-0.000000
DEBUG - b=0

海合会:

Target: x86_64-linux-gnu
--enable-languages=c,c++,fortran,objc,obj-c++ 
--prefix=/usr 
--program-suffix=-4.6 
--enable-shared 
--enable-linker-build-id 
--with-system-zlib 
--libexecdir=/usr/lib 
--without-included-gettext 
--enable-threads=posix 
--with-gxx-include-dir=/usr/include/c++/4.6 
--libdir=/usr/lib 
--enable-nls 
--with-sysroot=/ 
--enable-clocale=gnu 
--enable-libstdcxx-debug 
--enable-libstdcxx-time=yes 
--enable-gnu-unique-object 
--enable-plugin 
--enable-objc-gc 
--disable-werror 
--with-arch-32=i686 
--with-tune=generic 
--enable-checking=release 
--build=x86_64-linux-gnu 
--host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) 

标志:

CXXFLAGS += -g -Wall -fPIC

回答:

我已使用@amit 的答案来执行以下操作。

return (r>=(0.0-std::numeric_limits<double>::epsilon()));

这似乎有效。

【问题讨论】:

  • 行为不标准。您使用的是什么编译器和浮点数学选项。我怀疑使用 fastmath 的 g++
  • 另外,请发布一个完整且不太大的示例来展示该问题,并附上完整的构建说明,以便它可重现
  • 谢谢。事实证明(见我的回答),只有描述的行为,关于“负零”比较不等于零,这是非标准的。实际上,显然没有负零,只有一个非常小的负值,对于给定的输出格式,它呈现为前面带有减号的所有零数字。
  • @Cheersandhth.-Alf and hth 是的,这就是问题所在。我使用 std::numeric_limits::epsilon() 来帮助调整该问题。
  • 哦,我忘了说:因为“问题”只是对所发生的事情的感知,所以到目前为止,最好的行动方案很可能是什么都不做(除了可能关于演示文稿)。通过与 epsilon 和 nulling 进行比较,您可能引入了一个错误。具有讽刺意味的是,当在错误感知的背景下进行评估时,这可能看起来是正确的行为。

标签: c++ algorithm negative-number


【解决方案1】:

嗯,使用doubles 时的一般建议是记住它们并不准确。因此,如果平等很重要 - 通常建议使用一些容差因子。

在你的情况下:

if (|r - 0.0| >= EPSILON)

其中EPSILON 是您的容差因子,如果r 不为0.0 且至少有EPSILON 间隔,则结果为真。

【讨论】:

  • 谢谢。但我希望有一个更通用的解决方案。这可能是最好的方法,我将不得不继续寻找。
  • 更改我的问题以包含答案
  • 虽然正确且值得注意,但这并不能回答所提出的问题,因为它不区分 0.0-0.0;它们彼此相等,并且都小于 epsilon。
  • @RJFalconer 谢谢,我不知何故错过了一个绝对值,应该在那里。 (固定的)。另请注意,对于 r==0.0r== -0.0r &lt; 0.0 - EPSILON(当然,假设为正 EPSILON)。
  • 我认为这不对。对于r==-0.0r &gt; 0.0 - EPSILON。即使是最小的 epsilon (std::numeric_limits&lt;double&gt;::epsilon) 也小于 -0.0
【解决方案2】:

大概你的意思是if (r2==-0.0)。尽管如此,负 0 和正 0 将比较相等。出于所有意图和目的,两者之间没有区别。您可能不需要为负 0 设置特殊情况。您的比较 r &gt;= 0 should be true 为负 0 或正 0。

【讨论】:

  • 我假设相同,但是在测试时我遇到了r-0.0 并且返回语句与0.0 不同的情况。很奇怪,我知道。
  • @Justin 你有这方面的例子吗?听起来很奇怪。
  • 但是是什么让您认为r 是“负零”,而不仅仅是其他接近零的负数?如果是那些调试输出,请尝试使用 %g 而不是 %f...
  • 有趣。 %g 确实会产生不同的结果。 r=-2.78269e-07 而不是 -0.000000
  • @Justin 那么这只是一个很小的负数。 :)
【解决方案3】:

在某些较旧的系统上(即 IEE754 之前的版本),您可能会发现针对 0 的相等性检查对于负 0 失败:

if (a == 0.0) // when a==-0.0, fails

您可以通过在比较之前将 0.0 添加到某个值来解决此问题:

if ((a+0.0) == 0.0) // when a == -0.0, succeeds

不过,我要提醒的是,真正需要这样做的硬件/软件组合是相当不寻常的。我最后一次必须这样做是在 Control Data 大型机上。即使在那里,它也只是在一些不寻常的情况下出现:Fortran 编译器允许生成负零,并且知道在比较中补偿它们。作为计算的一部分,Pascal 编译器生成了将负零转换为正常零的代码。

因此,如果您在 Fortran 中编写了一个例程并从 Pascal 中调用它,您可能会遇到这个问题,并通过在进行比较之前添加 0.0 来防止它。

我会给出相当大的可能性,即您的问题并不是真正源于与负零的比较。我所知道的所有相当现代的硬件都会完全自动处理这个问题,所以软件根本不需要考虑它。

【讨论】:

  • 考虑到 OP 的新的更详细的例子,我认为你赢了 :-)
  • if (a == 0.0) // when a==-0.0, fails 在哪里?这违反了 IEEE 754。
  • @RJFalconer:如答案中所述,有问题的具体示例是控制数据大型机(比 IEEE 754 早几年)。正如我的最后一句话所说,这是在“所有合理的现代硬件”上自动处理的。下次您可能想在投票之前实际阅读答案。
  • 实际上,这并不是您在回答中所说的。你只是说这是你最后一次必须这样做。如果你澄清你的开场白,我会删除反对票。
  • @RJFalconer:我对您认为需要澄清的内容感到困惑。至于我所说的,这里有一个 CnP:“我所知道的所有相当现代的硬件都会完全自动处理这个问题,所以软件根本不需要考虑它。”
【解决方案4】:

考虑:

#include <stdio.h>
#include <iostream>
using namespace std;

int main()
{
    double const x = -2.78269e-07;

    printf( "printf: x=%f\n", x );
    cout << "cout: x=" << x << endl;
}

结果(使用 Visual C++ 11.0):

[D:\开发\测试] > cl foo.cpp foo.cpp [D:\开发\测试] > printf: x=-0.000000 cout: x=-2.78269e-007 [D:\开发\测试] > _

这似乎与问题中的神秘结果非常相似。

我的考虑是它嘎嘎作响,像问题的结果,看起来像问题的结果像问题的结果一样摇摆不定。

所以,我相信未显示的计算代码产生了值2.78269e-007


因此,总而言之,显然只有所描述的行为,即“负零”比较不等于零,这是不标准的。实际上,显然没有负零,只有一个非常小的负值。对于给定的输出格式,它显示为前面带减号的全零数字。

【讨论】:

    【解决方案5】:

    这只是一个很小的负数,控制台无法为您打印它
    您可以尝试根据您的精度进行检查,以便将其替换为纯零

    std::cout << std::setprecision(7) << (abs(value) < 0.0000005f ? 0 : value);
    

    注意我如何将 7 个位置添加到我使用 std::setprecision() 指定的浮动精度中。

    这取决于您希望打印浮点数/双精度的准确度。

    【讨论】:

      【解决方案6】:

      OP 在他的问题中提到了std::signbit,但它应该在一个很快就能找到的答案中,所以这里是:

      这从 C++11 开始就可以区分 -0.0+0.0

      #include <cmath>
      std::signbit(x) // true iif sign bit of x is set i.e. x is negative
      

      std::signbit on cppreference

      【讨论】:

      • 要将符号设为 -1 或 1,您需要: auto sgn = [](double x) { return 1 - 2 * std::signbit(x); };
      猜你喜欢
      • 1970-01-01
      • 2020-10-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-09
      • 2021-01-24
      • 2017-02-04
      • 1970-01-01
      • 2011-12-04
      相关资源
      最近更新 更多