我想确定(在c ++中)一个浮点数是否是另一个浮点数的乘法逆。问题是我必须使用第三个变量来完成它。例如这段代码:
float x=5,y=0.2;
if(x==(1/y)) cout<<"They are the multiplicative inverse of eachother"<<endl;
else cout<<"They are NOT the multiplicative inverse of eachother"<<endl;
将输出:“它们不是......”这是错误的,这段代码:
float x=5,y=0.2,z;
z=1/y;
if(x==z) cout<<"They are the multiplicative inverse of eachother"<<endl;
else cout<<"They are NOT the multiplicative inverse of eachother"<<endl;
将输出:“他们是......”这是正确的。
为什么会发生这种情况?
答案 0 :(得分:36)
您无法准确地比较浮点数。您无法精确地减去或除以它们。您无法准确计算任何。使用它们的任何操作都可能(并且几乎总是会)在结果中带来一些错误。即使a=0.2f
也不是一个精确的操作。这里的其他答案的作者很好地解释了其深层原因。 (感谢并为此投票。)
这是您的第一个也是更简单的错误。你永远不应该,从不,从不, 从不 ,从不在他们身上使用= =或其在任何语言中的等价物。
而不是a==b
,而是使用Abs(a-b)<HighestPossibleError
。
Abs(1/y-x)<HighestPossibleError
也行不通。至少,它不会经常工作。为什么?
让我们采用对x = 1000和y = 0.001。让我们采用y的“起始”相对误差为10 -6 。
(相对错误=错误/值)。
值的相对误差在乘法和除法时增加。
1 / y约为1000.它的相对误差是相同的10 -6 。 (“1”没有错误)
这使得绝对误差= 1000 * 10 -6 = 0.001。当您稍后减去x时,该错误将是剩下的全部。 (绝对错误在加法和减法时都会增加,而x的误差可以忽略不计。)当然,你不指望这么大的错误,HighestPossibleError肯定会设置得更低,你的程序会抛出一对好的x, y
因此,浮动操作的下两个规则:尽量不要将较大的值除以较小的值,并且上帝保存你减去之后的接近值。
有两种简单的方法可以解决此问题。
通过创建x的内容,y具有更大的abs值并将1除以更大的值,并且稍后才减去较小的值。
如果你想比较1/y against x
,当你正在使用字母,而不是值,并且你的操作没有错误时,将比较的两边乘以y
你有1 against x*y
。 (通常你应该检查那个操作中的符号,但是这里我们使用abs值,所以它很干净。)结果比较完全没有分区。
以较短的方式:
1/y V x <=> y*(1/y) V x*y <=> 1 V x*y
我们已经知道应该这样比较1 against x*y
:
const float HighestPossibleError=1e-10;
if(Abs(x*y-1.0)<HighestPossibleError){...
就是这样。
P.S。如果您确实需要一行 all ,请使用:
if(Abs(x*y-1.0)<1e-10){...
但它的风格很糟糕。我不建议。
P.P.S。在第二个示例中,编译器优化代码,以便在运行任何代码之前将z设置为5。因此,检查5对5即使是花车。
答案 1 :(得分:13)
问题是0.2
无法用二进制表示,因为它的二进制扩展具有无限数字位数:
1/5: 0.0011001100110011001100110011001100110011...
这类似于1/3
无法用十进制精确表示的方式。由于x
存储在具有有限位数的float
中,因此这些数字会在某些时候被截断,例如:
x: 0.0011001100110011001100110011001
问题出现是因为CPU内部通常使用更高的精度,因此当您刚刚计算1/y
时,结果会有更多数字,当您加载x
进行比较时,{{将扩展以匹配CPU的内部精度。
x
因此,当您进行直接逐位比较时,它们是不同的。
然而,在你的第二个例子中,将结果存储到变量意味着在进行比较之前它会被截断,因此以这种精度比较它们,它们是相等的:
1/y: 0.0011001100110011001100110011001100110011001100110011
x: 0.0011001100110011001100110011001000000000000000000000
许多编译器都有一些开关,你可以强制在每一步强制中断值,但是通常的建议是避免在浮点值之间进行直接比较,而是检查它们是否相差小于某个epsilon值,这是Gangnus is suggesting。
答案 2 :(得分:5)
您必须精确定义两个近似值对于乘法逆的含义。否则,你不会知道你应该测试什么。
0.2
没有确切的二进制表示。如果您存储的数字不具有精确限制的精确表示,则无法获得完全正确的答案。
同样的事情发生在十进制中。例如,1/3
没有确切的十进制表示。您可以将其存储为.333333
。但是你有一个问题。 3
和.333333
是否是乘法反转?如果您将它们相乘,则得到.999999
。如果您希望答案为“是”,则必须创建乘法逆的测试,这不像乘法和测试等式为1那样简单。
二进制文件也是如此。
答案 3 :(得分:2)
其他回复中的讨论很棒,所以我不会重复其中任何一个,但是没有代码。这里有一些代码可以实际检查一对浮点数乘以时是否恰好为1.0。
代码做了一些假设/断言(通常在x86平台上得到满足):
- float
是32位二进制(AKA single precision
)IEEE-754
- int
或long
是32位(我决定不依赖于uint32_t
的可用性)
- memcpy()
个副本浮动到整数/长整数,使8873283.0f变为0x4B076543(即某些&#34;字节顺序&#34;预期)
另外一个假设是:
- 它接收*
将乘以的实际浮点数(即浮点数的乘法不会使用数学硬件/库可在内部使用的更高精度值)
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <assert.h>
#define C_ASSERT(expr) extern char CAssertExtern[(expr)?1:-1]
#if UINT_MAX >= 0xFFFFFFFF
typedef unsigned int uint32;
#else
typedef unsigned long uint32;
#endif
typedef unsigned long long uint64;
C_ASSERT(CHAR_BIT == 8);
C_ASSERT(sizeof(uint32) == 4);
C_ASSERT(sizeof(float) == 4);
int ProductIsOne(float f1, float f2)
{
uint32 m1, m2;
int e1, e2, s1, s2;
int e;
uint64 m;
// Make sure floats are 32-bit IEE754 and
// reinterpreted as integers as we expect
{
static const float testf = 8873283.0f;
uint32 testi;
memcpy(&testi, &testf, sizeof(testf));
assert(testi == 0x4B076543);
}
memcpy(&m1, &f1, sizeof(f1));
s1 = m1 >= 0x80000000;
m1 &= 0x7FFFFFFF;
e1 = m1 >> 23;
m1 &= 0x7FFFFF;
if (e1 > 0) m1 |= 0x800000;
memcpy(&m2, &f2, sizeof(f2));
s2 = m2 >= 0x80000000;
m2 &= 0x7FFFFFFF;
e2 = m2 >> 23;
m2 &= 0x7FFFFF;
if (e2 > 0) m2 |= 0x800000;
if (e1 == 0xFF || e2 == 0xFF || s1 != s2) // Inf, NaN, different signs
return 0;
m = (uint64)m1 * m2;
if (!m || (m & (m - 1))) // not a power of 2
return 0;
e = e1 + !e1 - 0x7F - 23 + e2 + !e2 - 0x7F - 23;
while (m > 1) m >>= 1, e++;
return e == 0;
}
const float testData[][2] =
{
{ .1f, 10.0f },
{ 0.5f, 2.0f },
{ 0.25f, 2.0f },
{ 4.0f, 0.25f },
{ 0.33333333f, 3.0f },
{ 0.00000762939453125f, 131072.0f }, // 2^-17 * 2^17
{ 1.26765060022822940E30f, 7.88860905221011805E-31f }, // 2^100 * 2^-100
{ 5.87747175411143754E-39f, 1.70141183460469232E38f }, // 2^-127 (denormalized) * 2^127
};
int main(void)
{
int i;
for (i = 0; i < sizeof(testData) / sizeof(testData[0]); i++)
printf("%g * %g %c= 1\n",
testData[i][0], testData[i][1],
"!="[ProductIsOne(testData[i][0], testData[i][1])]);
return 0;
}
输出(见ideone.com):
0.1 * 10 != 1
0.5 * 2 == 1
0.25 * 2 != 1
4 * 0.25 == 1
0.333333 * 3 != 1
7.62939e-06 * 131072 == 1
1.26765e+30 * 7.88861e-31 == 1
5.87747e-39 * 1.70141e+38 == 1
答案 4 :(得分:0)
令人惊讶的是,无论舍入规则是什么,您都希望两个版本的结果相同(两次错误或右两次)!
最有可能的是,在第一种情况下,在评估x == 1 / y时,FPU寄存器中的精度提升,而z = 1 / y实际上存储了单精度结果。
其他贡献者已经解释了为什么5 == 1 / 0.2可能会失败,我不必重复。