首页/文章/ 详情

困扰Coder的浮点数

2天前浏览40

来看0.1+0.2是否等于0.3

fn main() {    
    assert!(0.1 + 0.2 == 0.3);
}
//‌ Rust中的assert!宏用于在代码中检查条件是否满足,
//‌ 如果条件为假,程序将触发一个panic并终止执行。

运行结果是

assertion failed: 0.1+0.2=0.3

显然,浮点运算0.1+0.2是不等于0.3的。在IEEE754标准下,这三个数都不能精确表示。浮点数是编程语言中常见的数据类型,计算机中是如何表示浮点数的?

基于IEEE754标准的浮点数表示

参照十进制科学计数法,二进制浮点数也可表示为

 

其中    叫做阶码,    叫做尾数。这样,表示一个浮点数需要分三个部分--符号、阶码和尾数。

▲图1

IEEE754标准中浮点数的表示格式如图1所示,32位的浮点数0-22位表示尾数,23-30位表示阶码,最高位表示符号,即0表示正数,1表示负数。

阶码采用移码表示,即在真值阶码的基础上加上一个偏移值(32位浮点数加127),所以实际的范围是    , 即    。这样可保证所有的阶码都是无符号整数,省去了表示符号的麻烦,且最小真值的移码为全0,最大真值的移码为全1,符合实际生活中的习惯。另外,移码保持了真值原有的大小顺序,采用移码表示还可以直观的比较大小,如图2所示,右起第一列是移码表示,具有和真值一样的大小顺序。

▲图2

尾数是定点小数,用原码表示。

IEEE754标准中浮点数分为规格化(Normalized)和非规格化(Denormalized)两种。规格化浮点数的尾数的范围是    ,即    ,非规格化浮点数的尾数的范围是    ,即    。这样表示尾数时还可以把小数点左边的那一位省去。

当尾数的23位bit位全为1,且阶码最大127时,通过变更符号位,可得到32位浮点数的表示范围约为    。注意规格数的尾数始终大于1,而阶码始终大于0, 即便    非常小, 但还是大于0。当尾数的23位bit位全为0,且阶码最小-126时,规格化浮点数能表示的最小正数就是    ,最大负数    。也就是说,使用规格数时, 我们除了无法表示0, 也无法表示    之间靠近0的极小数,这种现象叫做下溢(underflow),如图3所示

▲图3

这就是规格数的局限性, 这个局限性将由非规格数解决。

非规格数尾数的取值范围是    , 阶码固定为-126. 让尾数取    之间的数, 并通过变更符号位, 我们可以表示出    之间靠近0的极小数。特别地,当尾数的23位bit位全为0时,并通过变更符号位, 我们可以表示出+0和-0, IEEE754规范中也确实存在着这两种表示0的方式。

加入非规格化数后,IEEE754单精度浮点数的范围有如下变化

▲图4

非规格数的取值范围,正好可以卡在规格数取值范围的中间, 现在我们得到了一个完整的取值范围:    

▲图5

如图5所示,IEEE754标准中还有两类特殊数: Infinity和NaN。Infinity的表示很简单, 其符号位可正可负,8位阶码位全为1,且23位尾数位全部为0。

对于NaN,其符号位可正可负,8位阶码位全为1,23位尾数位不全为0。NaN表示“不是一个数”,例如负数的平方根就是NaN, NaN会影响其它数值,几平所有与NaN交互的操作都返回NaN,两个NaN值永远不相等

fn main(){
    let x =(-42.0_f32).sqrt();
    assert_eq!(x,x)
}

运行结果是

assertion `left == right` failed
  left: NaN
 right: NaN

回到开头的问题,十进制的0.1、0.2和于0.3怎么表示成IEEE754的浮点数?以下的python脚本可将十进制小数转为二进制


def dec2bin_ex(target):
    #将target分离成整数和小数部分
    i = int(target) #整数部分
    f = target - i  # 小数部分

    #将整数部分转换为二进制数
    a = []    #剩余部分的列表

    while i!= 0:
        a.append( i%2 ) # 余数
        i = i // 2      # 商

    #把元素按相反的顺序排列
    a.reverse( )


    #将小数部分转换为二进制数
    b = []   #带有整数部分的列表
    n = 0    #重复的次数,大于10次停止

    #乘以2直到小数部分为0
    while (f !=0):
        temp = f*2             #小数部分x2
        b.append( int(temp) )  #整数部分
        f = temp - int( temp ) #小数部分
        n += 1
        if( n >= 10):
            break

    #值转换为二进制, 结果格式为([整数部分],[小数部分]
    return a, b

例如,将10.625转为二进制

>>> dec2bin_ex(10.625)
([1,0,1,0], [1,0,1])

十进制数10.625转换为二进制数,即1010.101。那么0.1呢?

>>> dec2bin_ex(10.625)
([], [0,0,0,1,1,0,0,1,1,0])

如果保留小数点后十位,就是    。现在将其用IEEE754标准表示。

首先,移动小数点的位置,使二进制数的整数部分变成1。例如,将    的小数点向左移动3位,得到    。如果再乘以移动位数的权重,则为    ,这样就可以恢复到原来的值(参考十进制)。以同样的方式,我们用指数来表示    。这一次,我们将小数点向右移动4位,所以得到    

同样,对于    和    。无论你在小数点之后保留多少位,你都不能用精准的二进制数来代替它们。并且容器的大小是有限的,保留有限位数将产生非常小的误差。当然,即使是最先进的计算机也不能消除这种误差。重要的是,我们要明白,当我们与计算机互动时,会有误差。

怎么比较浮点类型的大小?

一般来说,测试数学运算是否在其真实数学结果的可接受范围内更安全。这个边界通常被称为    。Rust提供了一些可容忍的误差值,比如f32::EPSILON和f64::EPSILON

fn main(){
    let result:f32 = 0.1 + 0.2;
    let desired:f32 = 0.3;
    let abs_diff = (desired - result).abs();
    assert!(abs_diff < f32::EPSILON)
}


来源:数值分析与有限元编程
python
著作权归作者所有,欢迎分享,未经许可,不得转载
首次发布时间:2025-01-17
最近编辑:2天前
太白金星
本科 慢慢来
获赞 8粉丝 19文章 329课程 0
点赞
收藏
作者推荐

几何非线性| 应变张量

▲图1考虑二维空间中的一个连续体, 分别是其中的两个物质点,如图3.1所示。在连续体变形前( 时刻)引入物质坐标系 ,另外,在连续体变形之后( 时刻)引入空间坐标系 。两个坐标系相关的基向量分别为 和 。按照 描述,位置矢量 位移矢量 变形前后的位置矢量之间的关系为 使用坐标系 ,变形后的物体中任意点的位置矢量: 变形前的 在变形后移动到新的位置 ,记 于是 定义梯度算子 则 其中, 叫做变形梯度, 叫做位移梯度。由(3)可得 定义 应变 则 由(9)可得 则 展开,得 忽略高阶量 ,线性化的拉格朗日应变张量为 [例1] 给出如下的运动 则由 得 作求导运算 位移梯度 来源:数值分析与有限元编程

未登录
还没有评论
课程
培训
服务
行家
VIP会员 学习计划 福利任务
下载APP
联系我们
帮助与反馈