`

浮点数比较(转载)

    博客分类:
  • JAVA
阅读更多
该文章转载自:http://www.blogjava.net/shiliqiang/articles/285592.html
在数学运算当中经常会涉及到判断两个数是否相等的情况
对于整数很好处理 A==B这样的一个语句就可以解决全部的问题
但是对于浮点数是不同的

首先,浮点数在计算机当中的二进制表达方式就决定了大多数浮点数都是无法精确的表达的
现在的计算机大部分都是数字计算机,不是模拟机,数字机的离散化的数据表示方法自然无法精确表达大部分的数据量的。

其次计算机浮点数的精度在单精度float类型下,只有7位,在进行浮点运算的时候,这个精度往往会导致运算的结果和实际期望的结果之间有误差

因为前两个原因,我们很难用 A==B来判定两个浮点数是否相同

很自然,我们可以想到 fabs(A-B) < epsilon 这样的一种判别方法
但是这种判别方法稳妥吗?
它也不稳妥。

首先, epsilon是一个绝对的数据,也就是误差分析当中说说的绝对误差
使用一个固定的数值,对于float类型可以表达的整个数域来说是不可以的
比如epsilon取值为0.0001,而a和b的数值大小也是0.0001附近的,那么显然不合适
另外对于a和b大小是10000这样的数据的时候,它也不合适,因为10000和10001也可以认为是相等的呢
适合它的情况只是a或者b在1或者0附近的时候

既然绝对误差不可以,那么自然的我们就会想到了相对误差
bool IsEqual(float a, float b, float relError ) {
       return ( fabs ( (a-b)/a ) < relError ) ? true : false;
}
这样写还不完善,因为是拿固定的第一个参数做比较的,那么在调用
IsEqual(a, b, relError ) 和 IsEqual(b, a, relError ) 的时候,可能得到不同的结果
同时如果第一个参数是0的话,就有可能是除0溢出
这个可以改造
把除数选取为a和b当中绝对数值较大的即可
bool IsEqual(float a, float b, relError )
{
      if (fabs(a)<fabs(b)) return ( fabs((a-b)/a) > relError ) ? true : false;
      return (fabs( (a-b)/b) > relError ) ? true : false;
};

使用相对误差就很完善吗?
也不是, 在某些特殊情况下, 相对误差也不能代表全部
比如在判断空间三点是否共线的时候,使用判断点到另外两个点形成的线段的距离的方法的时候
只用相对误差是不够的,应为线段距离可能很段,也可能很长,点到线段的距离,以及线段的长度做综合比较的时候,需要相对误差和绝对误差结合的方式才可以
相对完整的比较算法应该如下:
bool IsEqual(float a, float b, float absError, float relError )
{
         if (a==b) return true;
        if (fabs(a-b)<absError ) return true;
        if (fabs(a>b) return (fabs((a-b)/a>relError ) ? true : false;
        return (fabs((a-b)/b>relError ) ? true : false;
}
这样才相对完整

这仅仅是浮点数之间最初级的比较方法
高级的方法看 浮点数比较(二) 这个文章 —— 如何把两个浮点数之间的比较转化成为两个整数之间的比较。
-------------------------------------

浮点数的内存结构


根据IEEE的标准,浮点数的定义如下

符号位指数位小数部分指数偏移量单精度浮点数双精度浮点数
1 位[31] 8位 [30-23] 23位 [22-00] 127
1 位[63] 11 位[62-52] 52 位[51-00] 1023

我们以单精度浮点数来说明:
符号位,表述浮点数的正或者负
指数实际也有正负的,但是没有单独的符号位,而是采用了一个偏移来表示
在计算机的世界里,进位都是二进制的,指数表示的也是2的N次幂
这个数据格式当中的,指数是8位,可表达的范围是0到255
而对应的实际的指数是-127到+128
这里特殊说明,-127和+128这两个数据在IEEE当中是保留的用作多种用途的
-127表示的数字是0
128和其他位数组合表示多种意义,最典型的就是NAN状态

小数部分,并不是一个浮点数的实际的小数
实际的小数在这个小数前面还保留了一个1
拿浮点数1.0来说
符号位是0, 实际指数是0,对应这里的指数就是127了,也就是0x7f
而小数部分就是1.0了, 1是暗含的不存储,实际的小数部分就是0了
因此组合起来的数据就是,0x3f80000

可以用一个类来表示:
class FloatType
{
public:
      union {
         DWORD m_dwInt;
         float          m_fFloat;
       struct {  

        int    m_nFra: 23;

          int    m_nExp : 8;

          bool m_bSign : 1;

      };
};

-----------------------------

参考IEEE的浮点数格式说明
对于0到1范围内的浮点数是可以压缩的

显然在0到1的范围内,一个单精度的浮点数,指数和符号位占据9个bit
而这9个bit是可以不用的,把它去除,只保留小数部分的23bit就可以达到压缩的目的
可以把一个浮点数从32bit,4字节压缩到23bit,3字节的范围内

这也是在3dmax等一些工具软件当中对浮点数进行压缩存储的方法。
比如,在单位化的法向量当中,每个浮点数都是0,1范围之间的数据
正常情况下表示三维空间当中的单位化法向量就需要12个字节
而经过这个压缩处理,只需要9个字节

-----------------------------


在写了上篇 浮点数的比较 以及 浮点数内存结构 两篇文章后
对于浮点数的比较有新的想法

我们先看正数的情况
根据IEEE的内存结构, 指数在高位,尾数在低位
浮点数大的对应的把其内存结构按照整数来理解进行比较的时候,情况也是成立的
因此在这里如果把他们进行比较的话,作为整数运算效率会非常的高,比如
float f1 = 1.23;
float f2 = 1.24
f1 > f2 成立
(int&)f1 > (int&)f2 也是成立的

而且,仔细研究IEEE的浮点结构,可以发现在《浮点数比较》当中提到的浮点数精度的问题——不是所有的浮点数都可以精确的表达
可以精确表达的浮点数实际上是有限的,就是那些IEEE的各种情况的枚举了 2^32个。不能表达的占据了大多数

IEEE在32位的情况下,尾数是23位的(暗含了第一个位数是1)
对于可以精确表达的浮点数来说,如果我们把这23位当作整数来理解, 它加1,就意味着可以找到比当前对应浮点数大的最小的浮点数了
反之,我们把两个浮点数,对应的整数做差值运算,得到的整数表明的是两个浮点数之间有多少个实际可以表达的浮点数(对应的指数相同的情况下很好理解;指数不同的时候,也是同样有效的)

这样,对于两个正的浮点数,他们的大小比较就可以用 (int&)f1 - (int&)f2 来进行比较了
差值的结果实际上就应该是相对误差了
这个相对误差,不等同于普遍意义上的相对误差
它所表达的是,两个浮点数之间可能还有多少个可以精确表达的浮点数
这样通过指定这个阈值来控制两个浮点数的比较就更有效了
对于两个正的浮点数
bool IsEqual(float f1, float f2, int absDelta)
{
     if ( abs ( (int&)f1 - (int&)f2 ) < absDelta ) return true;
}
这里用abs而不是fabs这在asm上面的运算差距也是很大的了

对于两个负数进行比较的情况也是相同的
只不过负数内存对应的整数加1,相应的找到的是更小的负数而已

但是负数和整数之间现在还不能进行直接的比较,因为根据IEEE的内存结构
正数和负数是不同的,对应的整数不能连续
正的最小的数就是0了,对应的整数也是0x00000000
负的最小的数就是-0,对应的整数则是0x 80000000
不用奇怪-0
在IEEE的表达当中是有两个0的,一个是 +0 一个是-0

有趣的是,按照 f1 == f2 的判断 +0和-0是相等的

通过对比我们可以发现,
+0 和正的浮点数可以按照转换成为整数的方式直接进行比较
-0 和负的浮点数可以按照转换成为整数的方式直接进行比较

如果我们能够把他们连接起来,整个整数方式的直接比较就完备了
对比一下负数的结构, 可以找到一个简单的办法了:
        把负数内存对应的整数减去 -0 ,他们就连续了
而且更好的结果是,所有的负数经过这次减法后,对应的整数也都是负数了
这样整个整数比较就变得连续了,而且在整个浮点数范围内都是有效的了

最后的比较算法就是:
// 函数:   bool IsEqual(float f1, float f2, int absDelta)
// 功能:把比较两个浮点数是否近似相同
// 输入:f1, f2参与比较的两个浮点数
//               absDelta 两个浮点数之间允许有多少个其他可以精确表达的浮点数存在,相当于相对误差
// 输出:   true,两个浮点数进行相等; false 两个浮点数不等
// 注意:仅仅适合IEEE 32位浮点数结构
bool IsEqual(float f1, float f2, int absDelta)
{
       int i1, i2;
       i1 = ( f1>0) ? ((int&)f1) : ( (int&) f1 - 0x80000000 );
       i2 = (f2>0) ? ((int&)f2) : ( (int&) f2 - 0x80000000 );
       return   ((abs(i1-i2))<absDelta) ? true : false;
}
分享到:
评论

相关推荐

    转载的可视化计算器编程

    这篇文章将探讨“VC可视化科学计算器”的编程概念,虽然描述提到这是一个转载的作品,但我们仍能从中学习到一些关键的技术点。 1. **图形用户界面(GUI)设计** GUI是可视化计算器的核心,它提供了直观的按钮和...

    2010计算机组成原理考研大纲(转载).pdf

    在数据表示与运算部分,考生需掌握数在机器中的不同表示方法(如二进制、八进制、十进制和十六进制间的转换),真值和机器数的概念,定点数和浮点数的表示及运算。浮点数运算涉及到IEEE754标准,加减运算的步骤和...

    这计算机组成原理考研大纲(转载).pdf

    本章涵盖了数在机器中的表示方法(如二进制、真值和机器数、BCD编码、字符与字符串),以及定点数和浮点数的运算。这部分知识常出现在选择题中,尤其是数制转换和定点数运算,包括移位运算、加减运算及溢出判断。...

    转载object pascal语法介绍

    11. Single:4字节浮点数。 12. ByteBool:1字节布尔值。 这些数据类型为开发者提供了处理不同类型数据的能力,确保了程序内存分配的正确性和效率。 总的来说,Object Pascal通过其面向对象的特性、丰富的数据类型...

    Python 核心编程代码 https://blog.csdn.net/weixin-38566632/article/deta

    1.2 浮点数 float 1.3 布尔类型 bool 1.4 字符串 str 1.5 列表 list 1.6 元组 tuple 1.7 集合 set 1.8 字典 dict 2. 逻辑结构、文件操作 2.1 分支结构和三元表达 2.2 循环和遍历 2.3 目录和路径 2.4 文件操作 3. ...

    pocketc 教程 转载 小羊编写

    - **`float`**:4字节的浮点数。 - **`char`**:2字节的字符类型。 - **`string`**:字符串类型,用于处理文本信息。 - **`pointer`**:指针类型,用于指向内存中的某个位置。 ##### 2.2 运算表达式 - **基本运算**...

    SQL笔试题(转载的)

    1. **数据类型**:SQL支持多种数据类型,如整数(INT)、浮点数(FLOAT)、字符串(VARCHAR)、日期时间(DATETIME)等。理解各种数据类型的使用场景是SQL的基础。 2. **查询语句(SELECT)**:这是SQL中最常用的...

    使用gcc和glibc来优化程序 转载.docx

    对于浮点数操作,特别是当精度不重要时,可以考虑使用单精度(float)而非双精度(double),因为前者通常更快。 最后,程序员还可以通过其他方式协助编译器,比如使用静态链表(statically linked lists)代替动态...

    使用gcc和glibc来优化程序 转载.pdf

    - `__builtin_fabs`, `__builtin_fabsf`:计算浮点数的绝对值,针对不同精度提供优化。 3. **glibc优化** glibc作为C标准库,也提供了许多优化手段,如使用高效的内存管理函数,比如`malloc`和`free`的替代品,...

    转载:ARM裸奔程序如何使用C库

    例如,整数除法可以通过乘以一个倒数来实现,而浮点数除法则可能需要更复杂的算法。这通常需要对硬件的浮点单元(如果有的话)有深入的理解。 对于标准输出,因为没有操作系统提供的console,我们需要自定义一个...

    《计算机组成原理高分笔记》

    其中,天勤论坛(***)提供了这份资料,并明确指出转载请注明出处。 从提供的内容片段来看,这份笔记详细讲解了数据的表示与运算,特别是运算器的结构和功能。运算器是计算机硬件中的一个组成部分,主要负责算术...

    使用gcc和glibc来优化程序 转载 (2).pdf

    例如,`__builtin_alloca`用于在栈上动态分配内存,`__builtin_ffs`查找第一个设置的位,`__builtin_abs`和`__builtin_labs`提供整数绝对值,而`__builtin_fabs`系列函数用于浮点数的绝对值计算。这些内部函数通常比...

    printf的格式控制的完整格式(转载)

    - `l`: 对于整型数据,`l`表示输出`long`类型,对于浮点数,表示`double`类型。例如,`%ld`用于输出长整型,`%lf`用于输出双精度浮点数。 - `h`: 用于将整型格式字符修正为`short`类型。例如,`%hd`用于输出短整型...

    JavaScript 浮点 运算 函数

    此函数是我自己写的,虽然在网上可以搜到很多,不过我找到的都是在算法中存在基本的浮点数的运算,导致结果仍然是错误的。由于刚刚学写JS,所以可能考虑不够周全,望大家批评指正。 代码中加了四舍五入函数,是网上...

    Matlab 常用图像函数(二)(转载).doc

    首先,MATLAB默认将图像数据存储为双精度类型(double),这是一种64位浮点数,虽然精度高但占用存储空间较大。另一种常用的类型是无符号整型(uint8),每个数据只占1个字节,适用于存储颜色范围在0到255之间的图像数据...

    Scratch作品:多功能计算机

    支持字母代替数字,也支持浮点数和小括号。干货满满,欢迎转载,记得注明原作者。此后仍有各作品或有趣游戏,请关注原作者,且点赞加收藏,记得推荐好友。下载即可使用,快点来下载吧!

    ClosetPair最近点对问题

    对于每个条带,只考虑与条带边界距离小于d的点,这样可以大大减少比较的次数。在每个条带内部,我们可以使用线性搜索找到最近的点对。 2. **扫线算法**:首先按y坐标排序所有点,然后从最低点开始扫描。对于每个...

    Delphi 函数:双线性插值缩放图像

    其中i、j均为非负整数,u、v为[0,1)区间的浮点数,则这个像素得值 f(i+u,j+v) 可由原图像中坐标为 (i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)所对应的周围四个像素的值决定,即:  f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u...

    数据挖掘贝叶斯——一种实现

    在predictClass方法的实现过程中,使用到了DecimalCalculate类,该类是Java中的BigDecimal类的扩展,用于高精度浮点数的运算。该类的实现同本人转载的一篇博文:对BigDecimal常用方法的归类中的Arith类相同。 数据...

Global site tag (gtag.js) - Google Analytics