为什么不能使用"=="运算符判断两个浮点数是否相等

在大多数编程语言中,使用"=="运算符判断两个浮点数是否相等的结果都是难以确定的,并且几乎总是无意义的,在编程实践中应该完全禁止使用"=="运算符去判断两个浮点数是否相等。

一个问题及其分析

如若不信,可以先试着运行下面这段js代码,看看它的输出结果:

console.log(0.1 + 1 - 1 == 0.1);

在Chrome Console中,上面这句代码的输出是false,意味着(0.1+1-1)这个数不等于0.1! 看上去加减法互为逆运算的这一基本运算法则都被颠覆了,这究竟是什么原因呢?

计算机科学作为应用数学的一个分支,和纯数学相比有一个显著的特点,那就是无法摆脱物理规律限制。浮点数的计算精度及其带来的一系列问题正是这一特点在存储字长受限上的体现。在详细解读这句js代码之前,我们不妨先来看看另外一个问题:

先将有理数1/7 [无限循环小数 0.1(428571)] 以小数形式写在一张最多只能容纳9个数字的小纸条上,得到0.14285714;再将这个数加上10并把结果写到第二张同样长的纸条上,最后一个数字4由于纸条太短写不下,只好舍去,得到10.1428571;然后将第二张纸条上的数字减去10的结果写到第三张纸条上,得到0.1428571。此时,如果认为第一张纸条上的数字是1/7, 第三张纸条上的数字是(1/7 + 10 - 10), 就会得出(1/7 + 10 - 10)不等于1/7的荒谬结论!然而,不论是第一张还是第三张纸条上的数字,都不是1/7的精确数值,而是1/7的近似值,并且近似的精度还不一样,所以它们不相等。

尽管浮点数在计算机器中的表示比上面所举的纸条示例要更复杂,但Javascript里的(0.1+1-1)这个数之所以和0.1不相等,与上面例子中(1/7 + 10 - 10)之所以不等于1/7并无本质上的不同:两者都使用了有限空间来存储无限小数,必然只能存储一个近似值,而不同的计算过程影响了近似值的精度。显然,如果使用"=="运算符对两个有着不同精度的近似值逐位进行比较,有可能会得到false的判断结果。

下面具体分析在Javascript中浮点数0.1经过先加1再减1的操作过程后,最终如何得到了一个不同于0.1的数。

JavaScript的Number类型都是64位浮点数[1],按照IEEE 754标准,0.1的存储模式为:

  • 最高位(第63位)为符号位,0,表示是正数
  • 第52-62这11位为指数部分,01111111011,转成十进制数为1019,表示指数值为1019-1023=-4
  • 第0-51这52位为小数部分,1001100110011001100110011001100110011001100110011010 [十进制数0.1的二进制表示是无限循环小数 0.(1001), 第50-51位的10是由精确值的第50-52位011舍入(rounding)第52位上的1进位后得到], 这样,小数值为二进制数1. 1001100110011001100110011001100110011001100110011010 [根据标准,小数部分的最高位总为1,于是被省去,这里需要加回],将其转成十进制是一个非常接近1.6的数。最终,1.6*2^(-4)=0.1

1的存储模式为:

  • 符号位0
  • 指数值为0
  • 小数值为1. 最终,1*2^(0)=1

0.1+1的处理过程为: 将0.1的小数部分右移4位(可见部分的最高3位以及隐藏的最高位1),得到小数部分为0001100110011001100110011001100110011001100110011010 (第50-51位的10由精确值的第50-52位011舍入第52位上的1进位后得到),同时将指数值加上4变为0,让它与1对齐;将指数值设为0,再将小数部分相加,即为0.1+1的最后结果1.1 (这里由于1的小数部分仅有最高隐藏位为1,相加后正好被隐去,于是结果的小数部分无变化)。

1.1-1的处理过程为: 两者的指数值都为0,无需移位对齐;两者的小数部分的隐藏最高位相减后被抵消,1的小数部分全为0,所以其余位无变化;此时的小数部分的最高位不是1,需要进行规约化(normalize)使最高位为1,具体操作为左移4位,同时将指数值减去4变为-4,并在最低4位补0。最后结果的小数部分为1001100110011001100110011001100110011001100110100000。

可见,(1.1-1)的最低6位和0.1是不一样的,这就是(0.1 + 1 - 1 == 0.1)被判断为false的原因。

正确的比较方式

function fpeq(a, b) {
    let eps = 1e-08;
    return Math.abs(a-b) < eps;
}

console.log(fqeq(0.1 + 1 - 1, 0.1));

延伸阅读

  • 浮点数的二进制表示
  • "4.2.1 Single-Precision Calculations" in The Art of Computer Programming, Vol 2: Seminumerical Algorithms, Third Editioin, by D. E. Knuth, 1998
  • "2.4 Floating Point" in Computer Systems: A Programmer's Perspective, by R. E. Bryant and D. R. O'Hallaron, 2003


[1] http://www.w3schools.com/js/js_numbers.asp

时间: 2024-09-16 01:12:06

为什么不能使用"=="运算符判断两个浮点数是否相等的相关文章

php判断两个浮点数是否相等的方法

 本文实例讲述了php判断两个浮点数是否相等的方法.分享给大家供大家参考.具体分析如下: 由于浮点数直接用==判断是否相等是不完全正确的,所以下面给出了一个方法,先设定的一个精度,如果在精度范围内相等则认为相等,否则认为不能 <?php $delta = 0.00001; $a = 1.00000001; $b = 1.00000000; if (abs($a - $b) < $delta) { /* $a and $b are equal */ } ?> 希望本文所述对大家的php程序

php判断两个浮点数是否相等的方法_php技巧

本文实例讲述了php判断两个浮点数是否相等的方法.分享给大家供大家参考.具体分析如下: 由于浮点数直接用==判断是否相等是不完全正确的,所以下面给出了一个方法,先设定的一个精度,如果在精度范围内相等则认为相等,否则认为不能 <?php $delta = 0.00001; $a = 1.00000001; $b = 1.00000000; if (abs($a - $b) < $delta) { /* $a and $b are equal */ } ?> 希望本文所述对大家的php程序设

JavaScript中判断两个字符串是否相等的方法_基础知识

先将用户的输入值全部转换为大写(或小写),然后再行比较: var name = document.form1.txtUserName.value.toLowerCase(); if(name == "urname") { // statements go here. }       JavaScript有两种相等运算符.一种是完全向后兼容的,标准的"==",如果两个操作数类型不一致,它会在某些时候自动对操作数进行类型转换,考虑下面的赋值语句: var strA =

源码:判断两种颜色值是否为相似颜色

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="gb2312"> <head> <head&g

Delphi中判断两个时间差是否在一个指定范围内

WithinPastYears.WithinPastMonths.WithinPastWeeks.WithinPastDays ... 判断两个时间差是否在一个指定范围内 DateUtils.WithinPastYears(); DateUtils.WithinPastMonths(); DateUtils.WithinPastWeeks(); DateUtils.WithinPastDays(); DateUtils.WithinPastHours(); DateUtils.WithinPas

哈希法判断两个有根树是否同构

Description Some major cities have subway systems in the form of a tree, i.e. between any pair of stations, there is one and only one way of going by subway. Moreover, most of these cities have a unique central station. Imagine you are a tourist in o

php判断两个日期之间相差多少个月份的方法

  本文实例讲述了php判断两个日期之间相差多少个月份的方法.分享给大家供大家参考.具体实现方法如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /** * @author injection(injection.mail@gmail.com) * @var date1日期1 * @var date2 日期2 * @var tags 年月日之间的分隔符标记,默认为'-' * @return 相差的月份数量 * @example: $date1 = "

C# 判断两张图片是否一致的快速方法

 这篇文章主要介绍了C# 判断两张图片是否一致的快速方法,需要的朋友可以参考下 代码如下: #region 判断图片是否一致  /// <summary>  /// 判断图片是否一致  /// </summary>  /// <param name="img">图片一</param>  /// <param name="bmp">图片二</param>  /// <returns>是

java爬虫中如何判断两个URL是否属于同一网站

问题描述 java爬虫中如何判断两个URL是否属于同一网站 如何判断两个URL是否属于同一网站,爬虫中要剔除站外链接,应该要怎么做,两个url主域名不一样但属于同一网站,应该通过什么进行判断 解决方案 String url = "http://ask.csdn.net/questions/237143"; Pattern p = Pattern.compile("(?<=http://|\.)[^.]*?\.(com|cn|net|org|biz|info|cc|tv)