go语言基础之浮点数

go语言基础之浮点数

小数的浮点表示法

±x.yz*e±pq

IEEE754标准

img

32位单精度浮点数在内存中的存储方式

存储一个32位浮点数, 比如20.5, 在内存或硬盘中要占用32个二进制位,这32个二进制位被划分为3部分

img

这32个二进制位的内存编号从高到低 (从31到0), 共包含如下几个部分:

符号位: sign ,即图中蓝色的方块

​ 符号位: 占据最高位(第31位)这一位, 用于表示这个浮点数是正数还是负数, 为0表示正数, 为1表示负数

偏移后的指数位: biased exponent, 即图中绿色的方块

​ 这部分用于表示以2位底的指数, IEEE754规定, 指数位用于表示[-127, 128]范围内的指数。不过为了表示起来更方便, 浮点型的指数位都有一个固定的偏移量(bias), 用于使 指数 + 这个偏移量 = 一个非负整数.。这样指数位部分就不用为如何表示负数而担心了。 在32位单精度类型中, 这个偏移量是127. 在64位双精度类型中, 偏移量是1023。所以, 这里的偏移量是127。即, 如果你运算后得到的指数是 -127, 那么偏移后, 在指数位中就需要表示为: -127 + 127(偏移量) = 0

如果你运算后得到的指数是 -10, 那么偏移后, 在指数位中需要表示为: -10 + 127(偏移量) = 117,有了偏移量, 指数位中始终都是一个非负整数.

尾数位:fraction , 即图中红色的方块

​ 尾数位: 占据剩余的22位到0位这23位. 用于存储尾数.

在以二进制格式存储十进制浮点数时, 首先需要把十进制浮点数表示为二进制格式, 还拿十进制数20.5举例:

十进制浮点数20.5 = 二进制10100.1

然后, 需要把这个二进制数转换为以2为底的指数形式:

二进制10100.1 = 1.01001 * 2^4

注意转换时, 对于乘号左边, 加粗的那个二进制数1.01001, 需要把小数点放在左起第一位和第二位之间. 且第一位需要是个非0数. 这样表示好之后, 其中的1.01001就是尾数.

我们再来看看规范化之后的这个数: 1.01001 * 2^4

其中1.01001是尾数, 而4就是偏移前的指数(unbiased exponent), 上文讲过, 32位单精度浮点数的偏移量(bias)为127, 所以这里加上偏移量之后, 得到的偏移后指数(biased exponent)就是 4 + 127 = 131, 131转换为二进制就是1000 0011

现在还需要对尾数做一些特殊处理

隐藏高位1.

尾数部分的最高位始终为1. 比如这里的 **1.**01001, 这是因为前面说过, 规范化之后, 尾数中的小数点会位于左起第一位和第二位之间. 且第一位是个非0数. 而二进制中, 每一位可取值只有0或1, 如果第一位非0, 则第一位只能为1. 所以在存储尾数时, 可以省略前面的 1和小数点. 只记录尾数中小数点之后的部分, 这样就节约了一位内存. 所以这里只需记录剩余的尾数部分: 01001

所以, 以后再提到尾数, 如无特殊说明, 指的其实是隐藏了整数部分1. 之后, 剩下的小数部分

低位补0

​ 有时候尾数会不够填满尾数位(即图中的红色格子). 比如这里的, 尾数01001不够23位。此时, 需要在低位补零, 补齐23位.之所以在低位补0, 是因为尾数中存储的本质上是二进制的小数部分, 所以如果想要在不影响原数值的情况下, 填满23位, 就需要在低位补零。比如, 要把二进制数1.01在不改变原值的情况下填满八位内存, 写出来就应该是: 1.010 0000, 即需要在低位补0。同理, 本例中因为尾数部分存储的实际上是省略了整数部分 1. 之后, 剩余的小数部分, 所以这里补0时也需要在低位补0:

原尾数是: 01001(不到23位)

补零之后是: 0100 1000 0000 0000 000 (补至23位)

实验

所以根据上述浮点数存储原理,我们可以自己实现浮点数取整的方法。

我们以float32型的数据10.5为例:

首先用math.Float32bits()方法获取10.5的二进制表示为:01000001001010000000000000000000

其中符号位为0;偏移后的指数位为10000010;尾数位为01010000000000000000000

想要获取浮点数的整数部分,首先要获取指数位,我们以0到31来表示二进制数下标,那么指数位为第23到30位,可以采用先左移1位,在右移24位的方式获取n := uint(b << 1 >> 24)

如果n<127,说明指数位为负数,整数部分为0。

然后再看尾数位,根据上述的描述,尾数位隐藏了高位的1,所以在获取完前n-127位尾数位后,在补充上高位的1,最后不要忘记符号位。

func ParseInt(f float32) int {
	b := math.Float32bits(f)
	n := uint(b << 1 >> 24)
	if n < 127 {
		fmt.Println("整数部分为0")
		return 0
	} else {
		x := b<<9>>(32+127-n) | (1 << (n - 127))
		if b&(1<<31) != 0 {
			return int(x) * -1
		}
		return int(x)
	}
}

同理,我们也可以自己判断浮点数是否为整数。

需要注意的是,go语言中float32的最小可以表示的浮点数数为2^-23≈0.0000001192,小于这个值就无法表示了,所以当输入为1.00000001时,返回值为true;所以当输入为1.0000001时,返回值为false

func IsInt(f float32) bool {
	b := math.Float32bits(f)
	n := uint(b << 1 >> 24)
	if n == 0 {
		return true
	}
	if n < 127 {
		return false
	} else {
		if uint(b<<(9+n-127)) > 0 {
			return false
		} else {
			return true
		}
	}
}

参考文献:(IEEE754详解(最详细简单有趣味的介绍)_明月几时有666的博客-CSDN博客_ieee754

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值