关于c ++:为什么两个float类型变量具有不同的值


Why two float type variables have different values

本问题已经有最佳答案,请猛点这里访问。

我有两个大小接近1000的整数向量,我要做的是检查这两个向量的平方和是否相同。所以我写了以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
std::vector<int> array1;
std::vector<int> array2;
... // initialize array1 and array2, and in the experiment all elements
    // in the two vectors are the same but the sequence of elements may be different.
    // For example: array1={1001, 2002, 3003, ....}
   //               array2={2002, 3003, 1001, ....}
assert(array1.size() == array2.size());
float sum_array1 = 0;
float sum_array2 = 0;
for(int i=0; i<array1.size(); i++)
       sum_array1 +=array1[i]*array1[i];
for(int i=0; i<array2.size(); i++)
       sum_array2 +=array2[i]*array2[i];

我希望sum_array1应该等于sum_array2,但实际上在我的应用程序中,我发现它们不同于sum_array1 = 1.2868639e+009,而sum_array2 = 1.2868655e+009。我接下来要做的是将sum_array1sum_array2的类型更改为双类型,如下代码所示:

1
2
3
4
5
6
 double sum_array1 = 0;
    double sum_array2 = 0;
    for(int i=0; i<array1.size(); i++)
           sum_array1 +=array1[i]*array1[i];
    for(int i=0; i<array2.size(); i++)
           sum_array2 +=array2[i]*array2[i];

此时sum_array1等于sum_array2sum_array1=sum_array2=1286862225.0000000。我的问题是为什么会这样。谢谢。


浮点值的大小是有限的,因此只能以有限的精度表示实值。当您需要比存储精度更高的精度时,这会导致舍入错误。

特别是,当把一个小数字(比如你要求和的那些)加到一个大得多的数字(比如你的累加器)中时,与小数字相比,精度损失可能相当大,会产生很大的错误;并且错误会因顺序不同而不同。

通常,float的精度为24位,相当于大约7位小数。您的累加器需要10个小数位(大约30位),因此您将体验到这种精度损失。通常,double有53位(约16位小数),因此可以精确地表示结果。

64位整数可能是这里最好的选择,因为所有的输入都是整数。使用整数可以避免精度损失,但如果输入太多或太大,则会有溢出的危险。

如果不能使用足够宽的累加器,可以对输入进行排序,以便首先累积最小值;或者可以使用更复杂的方法,如Kahan求和。


在这两个循环中,您添加的数字相同,但顺序不同。一旦总和超过了可以用float精确表示的整数值,您将开始失去精度,并且总和可能会略有不同。

你可以尝试的实验:

1
2
3
4
float n = 0;
while (n != n + 1)
    n = n + 1;
//Will this terminate? If so, what is n now?

如果您运行这个,您会发现循环实际上终止了——这看起来完全是违反直觉的,但是根据IEEE单精度浮点运算的定义,这是正确的行为。

你可以尝试同样的实验,用double替换float。您将看到相同的奇怪行为,但这一次循环将在n大得多时终止,因为IEEE双精度浮点数可以实现更高的精度。


浮点表示(通常是IEEE754)使用有限位来表示小数,因此使用浮点数的操作会导致精度损失。

通常,与常识相反,如果a是一个浮点变量,那么像a == ((a+1)-1)这样的比较会导致false

解决方案:

要比较两个浮点,必须使用一种"精度损失范围"。也就是说,如果一个数字与另一个数字的差小于该精度损失范围,则认为该数字相等:

1
2
3
4
5
6
7
//Supposing we can overload operator== for floats
bool operator==( float lhs , float rhs)
{
    float epsilon = std::numeric_limits<float>.epsilon();

    return std::abs(lhs-rhs) < epsilon;
}


doublefloat具有更多的位,因此拥有更多的信息。当您向float中添加值时,它将在sum数组1与sum数组2的不同时间对信息进行四舍五入。

根据输入值的不同,当使用double作为float(如果值足够大)时,您可能会遇到相同的问题。

在网络上搜索"关于浮点数你需要知道的一切",可以让你很好地了解这些限制,以及如何最好地处理它们。