多行数学结果与单行不同

8

我在进行一些浮点数计算时遇到了问题,我发现如果我将我的计算放在一行中,tan()函数会得到-0,而如果我将其拆成两行,则会得到0。看一下这段代码:

float theta = PI / 2.f;
float p = (PI / 2.f) - theta;
float result = tan(p);

以上,p = -0,结果为-4.37...
float theta = PI / 2.f;
float p = PI / 2.f;
p -= theta;
float result = tan(p);

上面这个式子,p=0,结果=0。
有人能解释一下这个区别吗?我猜-0是导致tan()的结果变成这样,尽管我在谷歌上找不到任何解释为什么。为什么完全相同的计算跨越不同的行会得出不同的答案?
谢谢。

你是在问为什么有“-0”还是为什么“tan(-0)”会得到“-4.37”(这是错误的)? - apple apple
你是在询问允许这种情况在任何平台上发生的规则吗?还是在询问为什么它会在你特定的平台上发生?如果是后者,你使用的是哪个平台? - David Schwartz
可能是浮点数计算有问题吗?的重复。 - apple apple
我无法复现 tan 函数的行为。 - apple apple
似乎在将 #define 转换为浮点数时出现了某种转换问题。一些混淆也来自于 printf 打印的值与 MSVC 中本地视图所告诉我的值不同。printf("%2.f", tan(p)); 在控制台中打印为 0,但 MSVC 显示结果为 -4.27。无论如何,问题似乎已经解决了。 - taurous
我正准备发一份样例程序来重现这个问题……和解决方案。 #define PI 3.1412 是一个 double 字面量,它导致了这个问题的出现。在 double 中执行算术运算(而不是 float)可以解决这个问题。当然,将 PI 强制转换为 (float) 也可以是其中之一的解决方案。 - paulsm4
3个回答

6

可能是由于PI的类型不同导致的。

如果你使用double,它会变成float,然后结果将如你所表示的那样。

但如果PI是float,那么这两个测试场景是相等的。


谢谢,那似乎就是罪魁祸首了。我使用的 pi 是一个 #define。如果我使用 const float 来表示 PI,奇怪的行为就会消失。 - taurous
1
教训是:永远不要使用 #define,而是使用 const 变量 :) - Tas
是的,我正在使用 Allegro 5,并看到它们定义了一个 PI,所以出于方便起见我使用了它。因为这是一个C库,所以这是有意义的。我要自己创建一个const! - taurous
如果你使用“double”,它将转换为“float”是没有意义的。 - Eric Postpischil
另一个问题是C++允许实现在计算表达式时使用扩展精度。当执行赋值或强制类型转换时,必须丢弃此扩展精度。因此,即使PIfloat,也可能使用多余的精度来计算float p = (PI / 2.f) - theta;,但是p = PI / 2.f; p -= theta;必须产生PI / 2.f作为浮点数,然后减去theta;它不能使用多余的精度。 - Eric Postpischil

5

@Naor所说的可能是正确的,但我想补充一些内容。

你可能得到的不是-4.37xx,而是-4.37xxxe-xx,这是一个非常小的负数。

由于浮点数计算中总会出现误差,所以我认为没有必要修改你的代码。两种情况都是正确的。


啊,谢谢你还教了我一些东西。你说得对,这是一个非常小的数字。我没有意识到(也没有真正仔细看它..)它显示的是科学计数法。好知道。 - taurous

0

所以我认为发生的事情是:

在这两个例子中,PI都是一个定义,可能像这样定义:

#define 3.14 //and some more digits

在C++中,像这样的数字被视为双精度浮点数。
预处理后,这个表达式:
PI / 2.0f

将被视为双重类型的prvalue。这意味着这行代码隐藏了一个操作:

float theta = PI / 2.f;

这是一个将双精度转换为单精度的过程,在这种情况下肯定会失去一些精度。

在第一个例子中,这种情况也会发生:

float p = (PI / 2.f) - theta;

但在评估整个表达式之后才能执行。请注意,在此评估期间,(PI / 2.f)仍将是双精度值,但theta将是从浮点转换为双精度的值,这解释了与0.0略有差异的结果。

在您的最后一个示例中,您首先将(PI / 2.f)转换为浮点数:

float p = PI / 2.f;

在下一行中从浮点类型的theta中减去它。这应该得出0.0,不过编译器可能已经优化掉了; )。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接