如何检查浮动的依赖关系

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值,这是甘尼斯的建议是什么.


Dav*_*rtz 5

您必须精确定义两个近似值对于乘法逆的意义.否则,你不会知道你应该测试什么.

0.2没有确切的二进制表示.如果存储的数字没有精确表示且精度有限,则无法获得完全正确的答案.

十进制相同的事情发生.例如,1/3没有精确的十进制表示.您可以将其存储为.333333.但是你有一个问题.是3.333333乘法逆?如果你乘以它们,你得到.999999.如果您希望答案为"是",则必须创建乘法逆的测试,这不像乘法和测试等于1那样简单.

二进制也会发生同样的事情.