分享

计算机浮点数和存储和运算规则

 yliu277 2023-03-28 发布于湖北

1、概述:

众所周知,计算机只能识别二进制数据,即所有的十进制都需要转换成二进制才能在计算机中进行存储和运算,但是,十进制数有整数部分和小数部分,对于整数部分转换成二进制数的话,我们采用除2取余数法;小数部分的话我们采用乘2取整法;求出来后,我们对数字进行规范化处理;


2、来个例子:
把十进制小数6.36转换成二进制,具体怎么操作?

上述例子我们把一个带小数的的十进制转换成了二进制:但是有没有发现一个问题?就是小数部分是一个无限循环的,我们计算机一个存储单元可是存不下这么多数据的呀,虽然可以跨存储区,但是无限的二进制,计算机是存不下的,不可能你定义一个数,结果电脑内存或硬盘立马塞满导致死机吧?那怎么办呢?进行规范化处理

3、规范化处理

规范化处理是只将一个浮点数转换成二进制后,根据存储规则,通过符号位 阶码 尾数的形式表示该浮点数;

data = (-1)^s*M*2*E

在Java语言中,存储浮点数主要有两种基本数据类型:Float和Double

Float:通过Float定义的变量,占4个字节的存储空间,即二进制数的长度为32位

Double:通过Double定义的变量,占8个字节的存储空间,即二进制数的长度位64位

java基本数据类型:浮点型 data=(-1)^s*M*2*E

s表示符号位 0表示整数 1表示负数

M表示有效数字 称其为位数

E表示指数位,称其为阶码

例如:

十进制浮点数 6.625,写成二进制是 110.101,数据存储时要进行规范化处理,即把它变成data所指定的格式,让整数位只保留一位且为1,数字变成了 1.10101,相当于该小数向左移动2位,导致原来的数据变小,则需要在规划后的数据乘以 2^2 保持与原数据相等,1.10101 * 2^2 ,按照上面的data格式,则s=0, M=1.10101 , E=2

 float类型有效位是23位(有效位就是转化为 data=(-1)^s*M*2*E 格式之后小数点后面的位数)

double类型有效位是52位

float:4字节=32bit=32位二进制位
1位符号位8位指数位23位有效数字位
double:8字节=64bit=64二进制位
一位符号位11位指数位52位有效数字位

注意:计算机表示浮点数时,是用8位或者11位去存储指数部分,在8位指数位数值上面,表示0~255,但是我们同样需要有负指数,正负指数的位数量为了均等,各自一半,-127~128,0是特殊点,特殊处理。储存时候会加上127,这样就刚刚好是0~255;如果 E 为 11 位,它的取值范围为 0~2047,中间数为1023;这样就能很好的储存了,不然的话,需要判断符号位来判断数值的正负,如下图32位的长度表示浮点数时,我们可以得知当E 127>127则代表该数的阶码为正的,如果E 127<127则代表该数的阶码为负的,即01111111为正负数阶码的分隔值。64位的长度表示浮点数时,我们可以得知当E 1023>1023则代表该数的阶码为正的,如果E 1023<1023则代表该数的阶码为负的,即01111111111为正负数阶码的分隔值

4、浮点数的计算

不管任何一门计算机语言,在进行浮点数的计算时都会出现精度问题,下面来看一下Java和JS语言计算浮点数案例:

  1. public class test {
  2. public static void main(String[] args) {
  3. System.out.println(2.0F-1.5F);
  4. System.out.println(2.0F-1.3F);
  5. }
  6. }
  7. 执行结果:
  8. 0.5
  9. 0.70000005

 Js的计算结果

结论:

我们可以看到,执行结果和我们预期的十进制的计算结果不一样,让我来分析一下:在这里需要大家有点原码/反码/补码的知识;因为计算机不能像十进制一样识别减法操作,所以任何减法都要转换成补码后再相加,比如:A-B = [A]补 [-B]补,正数的原码反码补码都一样:

2.0F - 1.5F  十进制:2.0F
二进制:10.0
规范化:我们在此忽略符号位    
    原码:1.0000 0000 0000 0000 0000 000*2^1   //因为我们定义变量是Float类型,有效数据位为23位,所以需要补零让长度为23    
    补码:1.0000 0000 0000 0000 0000 000*2^1 
     
十进制:1.5F      
二进制:1.1     
 
规范化:    
原码:1.1000 0000 0000 0000 0000 000*2^0
反码:0.0111 1111 1111 1111 1111 111*2^0 //符号位不变,按位取反    
补码:0.1000 0000 0000 0000 0000 000*2^0 //补码 1 

我是转别人的博客我觉得这里应该先左移一位 再求补码 否则就得不到 11.01000000000000000000000*2^1 
    源码
    1.1000 0000 0000 0000 0000 000
    左移一位
    0.1100 0000 0000 0000 0000 000
    反码
    1.0011 1111 1111 1111 1111 111
    补码
    1.0100 0000 0000 0000 0000 000
   
计算:低阶向高阶看齐,即让2^0向2^1看齐,小数点左移一位则乘以2^1;
  01.00000000000000000000000*2^1
  11.01000000000000000000000*2^1  
————————————————————————————————   
       00.01000000000000000000000*2^1   //左边第一个1为符号位溢出去除
    
所以2.0F-1.5F结果: 0.01000000000000000000000*2^1 = 0.10000000000000000000000*2^0 
0.5F的浮点数表示:0.1000000000000000000000*2^0 
所以2.0F-1.5F = 0.5F

那为何2.0F-1.3F会不正确呢?

——————————————————————————————


采用相同的分析方法:2.0F - 1.3F  

十进制:2.0F  
二进制:10.0  
规范化:1.00000000000000000000000*2^1  

十进制:1.3F
二进制:1.0100 1100 1100 1100 1100 110

    左移一位
    0.1010 0110 0110 0110 0110 011
    反码
    1.0101 1001 1001 1001 1001 100
    补码
    1.0101 1001 1001 1001 1001 101
 

计算:    
       01.0000 0000 0000 0000 0000 000  
  11.0101 1001 1001 1001 1001 101 
———————————————————————————————————   
       00.0101 1001 1001 1001 1001 101

结果是 0.0101 1001 1001 1001 1001 101 * 2^1

对结果进行规范化(向右移动两位使整数位为1): 1.01100110011001100110100*2^-1  

0.7的表示: 
二进制:0.101100110011001100110011  
向右移动一位:1.0110011001100110011001100*2^-1

规范化处理时存在一个细节:0舍1入,因为是Float类型,所以规范化处理后,要保留23个有效位即小数点后要有23个数据,但是第24是1所以要往前 1;如果是0则直接舍去;

1.0110011001100110011001100 * 2^-1 这里的第24位是 0 所以不向前 1

规范化以后就是 1.01100110011001100110011 * 2^-1


也可以通过这个工具页面来转换   https:///floatconverter/

所以最终结果并不相等  即 1.01100110011001100110100*2^-1 不等于  1.01100110011001100110011*2^-1

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多