use*_*274 12 c++ algorithm floating-point inverse floating-accuracy
我想确定(在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;
Run Code Online (Sandbox Code Playgroud)
将输出:"他们不是......"这是错的,这段代码:
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;
Run Code Online (Sandbox Code Playgroud)
将输出:"他们......"这是正确的.
为什么会这样?
Gan*_*nus 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, ÿ
因此,浮动操作的下两个规则是:尽量不要将较大的估值值除以较小的值,并且上帝可以避免在此之后减去接近的值.
有两种简单的方法可以解决这个问题.
通过建立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
Run Code Online (Sandbox Code Playgroud)
我们已经知道这样的比较1 against x*y应该这样做:
const float HighestPossibleError=1e-10;
if(Abs(x*y-1.0)<HighestPossibleError){...
Run Code Online (Sandbox Code Playgroud)
就这些.
PS.如果你真的需要它都在同一行,使用:
if(Abs(x*y-1.0)<1e-10){...
Run Code Online (Sandbox Code Playgroud)
但这是不好的风格.我不建议.
PPS在第二个示例中,编译器优化代码,以便在运行任何代码之前将z设置为5.因此,检查5对5即使是花车.
ham*_*mar 13
问题是0.2无法用二进制表示,因为它的二进制扩展具有无限多个数字:
1/5: 0.0011001100110011001100110011001100110011...
Run Code Online (Sandbox Code Playgroud)
这类似于1/3无法用十进制精确表示的方式.由于x存储在float具有有限位数的位数中,因此这些数字会在某些时候被截断,例如:
x: 0.0011001100110011001100110011001
Run Code Online (Sandbox Code Playgroud)
出现这个问题是因为CPU内部通常使用更高的精度,所以当你刚刚计算出来时1/y,结果会有更多的数字,当你加载x它们进行比较时,它们x会被扩展以匹配CPU的内部精度.
1/y: 0.0011001100110011001100110011001100110011001100110011
x: 0.0011001100110011001100110011001000000000000000000000
Run Code Online (Sandbox Code Playgroud)
因此,当您进行直接的逐位比较时,它们是不同的.
但是,在第二个示例中,将结果存储到变量中意味着在进行比较之前会将其截断,因此在此精度下比较它们时它们是相等的:
x: 0.0011001100110011001100110011001
z: 0.0011001100110011001100110011001
Run Code Online (Sandbox Code Playgroud)
许多编译器都有开关,你可以强制中断值在每一步都被截断以保持一致性,但通常的建议是避免在浮点值之间进行直接比较,而是检查它们是否相差小于某个epsilon值,这是甘尼斯的建议是什么.
您必须精确定义两个近似值对于乘法逆的意义.否则,你不会知道你应该测试什么.
0.2没有确切的二进制表示.如果存储的数字没有精确表示且精度有限,则无法获得完全正确的答案.
十进制相同的事情发生.例如,1/3没有精确的十进制表示.您可以将其存储为.333333.但是你有一个问题.是3和.333333乘法逆?如果你乘以它们,你得到.999999.如果您希望答案为"是",则必须创建乘法逆的测试,这不像乘法和测试等于1那样简单.
二进制也会发生同样的事情.