浮点数的那点事

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浮点数的那点事相关的知识,希望对你有一定的参考价值。

浮点数是计算机中储存实数的形式。我们时常需要用浮点数去处理带小数点的运算。可你是否知道,浮点数还有这些操作:

正负无穷大

与整数不同浮点数没有溢出的概念当浮点数的运算结果超过一定范围时它的值就会根据运算结果的符号变成正无穷大或负无穷大最简单产生无穷大的运算就是除以0.例如1.0/0.0的结果是正无穷大-1.0/0.0的结果是负无穷大注意浮点数除于0不会产生错误,只有整数才会。

无穷大的性质和你想象中的一样:除了无穷大自身和后面要提到的NaN外,任何一个浮点数都小于正无穷大,大于负无穷大。而且无穷大加、减、除一个非无穷大,或乘一个非0数的结果都是无穷大(可能变号)。

无穷大在一些特定情况很有用。比如说,你要找xx的最小值时,你可以把储存最小值变量初始化为无穷大。或者,你也可以用无穷大的代价表示无解。

NaN

NaN是浮点数中一个有毒的常量。它满足很多奇葩的性质。

NaN是Not a Number的缩写也就是说NaN表示它不是一个数字浮点数中居然有一个“不是数字”的“数字”究竟是几个意思呢

首先我们看看NaN是怎么产生的NaN对应着数学中的“不定式”例如0.0/0.0,(1.0/0.0 - 1.0/0.0)(即无穷大减无穷大),0.0*(1.0/0.0)(即0乘以无穷大)等。也就是说,当运算结果不能确定时,它就干脆直接给你一个“不是数字”。

由于NaN不是数字,它满足很多毁三观的性质,其中一个标志性的性质,就是它与任何数字都不相等,包括它自己!

所以,当xNaN时,x == xfalse

这是判断一个浮点数是不是NaN的常用方法。事实上,除了!=的结果永远是true以外,NaN与任何浮点数的所有大小比较都是false。而且,NaN和任何浮点数的运算(即加减乘除等)结果都是NaN

由于NaN的性质如此有毒,它很容易造成各种bug。例如,如果你不小心计算了无穷大减无穷大,你就得到NaN。由于它的所有比较都是false,你的程序会出现奇怪的行为,让你百思不得其解。

还有就是,如果你看到如下if语句

if (x < 1.2) {
    // Do something
} else if (x >= 1.2) {
    // Do something
}

你可能以为else后面那个if语句是多余的。其实不然。这个if起到了排除NaN的作用。尤其是当x是函数参数的时候,如果参数传入一个NaN,你要能正确处理它。

当然,NaN也有一定的正面作用。例如我们可以用NaN来表示“无解”或者“不存在”等性质。我们也可以利用它和任意数字不相等这一性质来提供便利。例如如果我们想从浮点数组中删除一个数字,我们可以把它设为NaN。特别的,在C++中我们可以用memset(arr, 0xFF, sizeof(arr))将浮点数组初始化为NaN

精度误差

由于储存空间有限,我们不可能将一个实数,尤其是无限小数的精确值储存下来。这样,在运算过程中就会产生精度的误差。

我们不妨看这样一段代码:

for (int i = 1; i < 100; ++i) {
    double a = 1.0/i;
    if (a*i != 1.0)
        cout << " " << i;
}
cout << endl;

假如没有精度误差上面这段程序应该什么都不输出但实际上它会输出4998。这是精度误差导致的相等判断错误。

所以,两个浮点数是否相等并不能直接判断,而应该用两个数的差的绝对值小于一个很小的数(例如1e-77代表精确的小数位数)作为条件来进行判断。

const double eps = 1e-7;
cout << "inexact result with double:";
for (int i = 1; i < 100; ++i) {
    double a = 1.0/i;
    if (!(fabs(a*i - 1.0) < eps))
        cout << " " << i;
}

这段代码就不会有任何输出了。

 

以上是关于浮点数的那点事的主要内容,如果未能解决你的问题,请参考以下文章

python与中文的那点事

关于JavaScript的作用域你应该了解的那点事!

this的那点事

重载delete时的那点事

for循环,定时器,闭包混合一块的那点事。

TODO:字节的那点事Go篇