对内存里float4字节的好奇

初学计算机都要学那个什么二进制十进制什么补码 反码那些玩意儿哈,由于最近要做一个单片机往另外一个单片机发数据的需求,直接c语言指针 然后float4字节传过去不就得了吗,麻烦就麻烦在这里 另一端编程机是个啥鸟lua 麻烦的一逼,integer这种我们就不说了哈因为实在是太直白了没啥技术含量,我们今天来啃float这个硬骨头。你知不知道什么叫ieee754 。float到底可表示的范围是多少到多少。以前听过一个老手讲的课 ,说实话这玩意儿编程多年的老手 说的都模棱两可。当我啃着感觉稍微有点硬了 又不断的查资料 探索。我知道我又得写一篇博文以做记录了。还好不算很硬。没经过多少捣鼓就出来了。c#这玩意儿 用着还真是顺滑,当然纯c嵌入式我也干了一年多了 对这种“低级语言”以及计算机底层又有了稍微深刻一点的认识了。这么多年了c#用顺手了 习惯用它做基础算法和逻辑验证 ,然后移植为其它语言的。

关于ieee754的资料网上大把的 你就随便搜一篇吧 比如这:

https://blog.csdn.net/MaTF_/article/details/124842807

在线测试工具:

https://www.h-schmidt.net/FloatConverter/IEEE754.html

我们也是看了后 照着原理用代码实现的。

有没有想过c语言以及其他高级语言里编程基础里的float数据类型的4个字节在计算机里到底是怎么转换显示在你屏幕上的 是不是有时候我们从来没想过一个东西是怎么来的。float是4字节的,那么我们给一串4字节。如果是c#你还不知道有bitconverter这个函数怎么办?

我自己参考然后成功实现了过后的一些理解

看 整体概览中心思想 还是跟我们十进制一样的  底数+指数的形式 第一个有效数字肯定是1 开始的 所以最前面一位去掉(解析的时候默认它是有的)比如 1x10^3  这种形式。只不过我们这里的 指数和底数 都是二进制。小数部分 代码处理 为什么是负的次方 ,稍微停顿下 11.01 二进制还有小数这个比较费解,那么通行于二进制整数的规则 进位则x2  ,那么小数部分则是往后一位则/2 想想我们十进制数 2的负2次方 就是 1/(2x2) 就是四分之一   是不是啊   。那么我们这里也是同样的道理。

指数部分 ,这里也是二进制的指数 不是10进制的   ,这里有8位 那么 就是 底数部分可以x2^-127 到128 次方 。虽然第一次理解有点别扭 ,稍微梳理下 整体感觉还是比较顺畅的。说明计算机科学家还是经过深思熟虑考虑过的。

关于数值精确表示与非精确表示

然后另外一个 ,基于这种原理 机制,活了这么多年 你才发现 这个float有时候 并不能 精确表示一个数  0.125 这种 还好说,为啥能够精确标识啊,你看他小数点往后完全符合描述的 -2次方 也就是二分机制 ,相信通过上面那些理解  不用我搬那些高深的理论 讲解你也能够明白  从1 分下来 0.5  0.25 0.125   刚好分完。

看一个不能够精确分完的1567.37      -> 1567.36987304688 看  是不是很神奇的事情出现了  ,这不是bug  就是由于他机制本身的原因所致的。我们不能改变它  就只能与他共存。 就像有理数除某些数除不尽 一样的 这里也是机制本身决定的 暂且理解为类似的东西吧。

下面是阅读了上面的参考文献后经过验证的代码成功实现

我代码里注释已经写得相当详尽了

  1 //ieee754 格式float解析
2 public void iee754BytesToVal(byte[] bytes)
3 {
4 //所有的位序列
5 bool[] bits = new bool[32];
6
7
8
9 //先进行翻转
10 Array.Reverse(bytes);
11
12 //进行数据预处理
13 int bitarIndx=0;
14 for (int i = 0; i < 8; i++)
15 {
16 bits[bitarIndx++] = (bytes[0] & (0x80>>i))>0?true:false;
17 }
18
19 for (int i = 0; i < 8; i++)
20 {
21 bits[bitarIndx++] = (bytes[1] & (0x80 >> i)) > 0 ? true : false;
22 }
23
24 for (int i = 0; i < 8; i++)
25 {
26 bits[bitarIndx++] = (bytes[2] & (0x80 >> i)) > 0 ? true : false;
27 }
28
29 for (int i = 0; i < 8; i++)
30 {
31 bits[bitarIndx++] = (bytes[3] & (0x80 >> i)) > 0 ? true : false;
32 }
33
34 for (int i = 0; i < bits.Length; i++)
35 {
36 Console.Write(bits[i] == true ? "1" : "0");
37 Console.Write(" ");
38 }
39
40
41 //获取某个位 与上 指定的位
42 //获取符号位
43 int singl = -1;
44
45 if (bits[0]== true)
46 {
47 singl = -1;
48 Console.WriteLine("负数");
49 }
50 else
51 {
52 singl = 1;
53 Console.WriteLine("正数");
54 }
55
56
57 //阶码0 1字节
58 //取出对应的阶码位 7f80
59
60 sbyte exponent = 0;
61 for (int i = 0; i < 8; i++)
62 {
63 byte bitSetPoint=0x00;
64 if( bits[1+i]==true)
65 {
66 bitSetPoint = 0x80;
67 }
68 else
69 {
70 bitSetPoint = 0x00;
71 }
72
73 exponent = (sbyte)(exponent | (bitSetPoint >> i));
74
75 }
76
77
78 //0x7f
79 sbyte exponentID = 0x7f;
80 sbyte exponentReal = (sbyte)(exponent - exponentID);
81
82
83 //尾数 23位
84 double mantissa=0;
85 for (int i = 0; i < 23; i++)
86 {
87 if(bits[9+i]==true)
88 {
89 mantissa = mantissa + Math.Pow(2, -(i + 1));
90 }
91 else
92 {
93 mantissa = mantissa + 0;
94 }
95 }
96 mantissa = (1 + mantissa) * singl * Math.Pow(2, exponentReal);
97
98
99 Console.WriteLine("最终的数是:" + mantissa);
100
101 }
  1 public void iee754ValToBytes(float val)
2 {
3 Console.WriteLine(val.ToString());
4 string valStr = val.ToString();
5
6 //符号位
7 int singl = 1;
8 if (valStr.IndexOf('-') != -1)
9 {
10 singl = -1;
11 valStr.Replace("-", "");
12 }
13 else
14 singl = 1;
15
16 string[] valPartStrs = valStr.Split('.');
17
18 string frontPartStr = "0";
19 if (valPartStrs.Length > 0)
20 frontPartStr = valPartStrs[0];
21 string afterPartStr = "0";
22 if (valPartStrs.Length > 1)
23 afterPartStr = valPartStrs[1];
24
25 //整数部分处理
26 List<bool> frontBits = new List<bool>();
27 int frontNum = int.Parse(frontPartStr);
28 if (frontNum != 0)
29 {
30
31
32 //整数部分 采用短除法
33 long dividend = frontNum;
34 int indx = 0;
35 do
36 {
37 long yu = dividend % 2;
38 dividend /= 2;
39 frontBits.Add(yu == 1 ? true : false);
40 } while (dividend > 0);
41 indx = 0;
42
43 //注意这里有一个反转 整数部分短除法 和小数部分的x2取整不一样的
44 frontBits.Reverse();
45
46 Console.WriteLine("整数部分");
47 for (int i = 0; i < frontBits.Count; i++)
48 {
49 Console.Write(frontBits[i] == true ? "1" : "0");
50 Console.Write(" ");
51 }
52 Console.WriteLine();
53 }
54
55 // 小数部分采用*2取整方法
56 List<bool> afterBits = new List<bool>();
57 int afterNum = int.Parse(afterPartStr);
58 if (afterNum != 0)
59 {
60 afterPartStr = "0." + afterPartStr;
61
62
63
64 float afterApendOne = float.Parse(afterPartStr);
65 for (int i = 0; i < 23 - frontBits.Count; i++)
66 {
67
68 afterApendOne = afterApendOne * 2;
69 if (Math.Floor(afterApendOne) == 1)
70 afterBits.Add(true);
71 else
72 afterBits.Add(false);
73 string[] tmpxiaoshu = afterApendOne.ToString().Split('.');
74 if (tmpxiaoshu.Length > 1)
75 {
76 afterApendOne = float.Parse("0." + tmpxiaoshu[1]);
77 if (afterApendOne == 0)
78 break;
79 }
80 else
81 {
82 break;
83 }
84 }
85
86 }
87 //指数部分
88 sbyte exponent = (sbyte)((sbyte)127 + (sbyte)frontBits.Count - 1);
89
90 //总览数据----------------------------------------------------------------------
91 List<bool> finalBits = new List<bool>();
92 //附上符号位
93
94 if (singl > 0)
95 finalBits.Add(false);
96 else
97 finalBits.Add(true);
98
99
100 Console.WriteLine("指数部分");
101 for (int i = 0; i < 8; i++)
102 {
103 bool exponentBit = (exponent & (0x80 >> i)) > 0 ? true : false;
104 finalBits.Add(exponentBit);
105
106 Console.Write(exponentBit == true ? "1" : "0");
107 Console.Write(" ");
108 }
109 Console.WriteLine();
110
111 //附上整数部分
112 for (int i = 1; i < frontBits.Count; i++)
113 {
114 finalBits.Add(frontBits[i]);
115 }
116
117 //附上小数部分
118 for (int i = 0; i < afterBits.Count; i++)
119 {
120 finalBits.Add(afterBits[i]);
121 }
122
123
124 //IEEE754 float 标准 32位 不足的补0
125 Console.WriteLine("---------------------------------");
126 for (int i = 0; i < finalBits.Count; i++)
127 {
128 Console.Write(finalBits[i] == true ? "1" : "0");
129 Console.Write(" ");
130 }
131 if (finalBits.Count < 32)
132 {
133 int beaddcount = 32 - finalBits.Count;
134 for (int i = 0; i < (beaddcount); i++)
135 {
136 finalBits.Add(false);
137 Console.Write("0");
138 Console.Write(" ");
139 }
140 }
141 Console.WriteLine();
142 Console.WriteLine("---------------------------------");
143
144 //利用前面的例子进行反向转换测试
145
146 UInt32 reconvert = 0x00000000;
147
148
149 for (int i = 0; i < 32; i++)
150 {
151 UInt32 bitSetPoint = 0x00000000;
152 if (finalBits[i] == true)
153 {
154 bitSetPoint = 0x80000000;
155 }
156 else
157 {
158 bitSetPoint = 0x00000000;
159 }
160 reconvert = reconvert | (bitSetPoint >> i);
161 }
162
163 byte[] recdata = BitConverter.GetBytes(reconvert);
164
165 Console.WriteLine("-------------开启再次转换过程--------------------");
166 iee754BytesToVal(recdata);
167 }

那么怎么验证我们的算法是正确的呢,很简单啊把我们拿出去的float变量转的bytes 再转float 结果一致就代表成功了,我们也可以利用c#自带的BitConverter.GetBytes(float)得到的4字节进行验证。

 1 iee754ValToBytes(1567.37f);
2 //floattobytes(19.625f);
3 return;
4 float f = -7434.34f;
5 byte[] floatar = BitConverter.GetBytes(f);
6 Console.Write("{0:X2}", floatar[0]);
7 Console.Write("{0:X2}", floatar[1]);
8 Console.Write("{0:X2}", floatar[2]);
9 Console.Write("{0:X2}", floatar[3]);
10 Console.WriteLine();
11 iee754BytesToVal(floatar);

关于代码的正确性已经毋庸置疑了哈,文章开头的图已经给出结果了。

关于通用性

首先所有的编程环境都遵循这个标准 ,不管你c c++ c# java ,c# 里提取的bytes 放到 c++下去解析 是能解析出来的 已经测试过了(都不用解析 就是一个指针内存操作),Java我没试过相信是一样的。关于c++的处理 , 看c++指针直接操作内存的优势和 便利性就出来了。

c语言里获取的字节码转换为float:

1 float channelUpLimit = *(float *)&value[0];

float转换为字节以相反方式操作就可以了,指针用伪数组操作方式就可以了,你懂的,c语言特别善于玩儿这种内存控制。

编写代码时精度问题的陷阱

这又隐申出另外的问题,就是编程语言的数制精度问题。c语言中 float fff = 4796 / 10.0;得到的不是479.6 而是个不知道什么的玩意儿 479.600006 无论用 什么floor这些函数*10+0.5 又/10 处理都相当棘手。网上说用double 可以避免很多问题 ,试了下 用 double fff = 4796 / 10.0; 得到的确实是479.600000

https://www.yisu.com/zixun/371395.html

老早就看到前同事在代码中写一些这种玩意儿 ,刚入行不久一脸懵逼 这是什么神经病代码

1 float a=0, b=0, c=0;
2 if (a - b < 0.00001)
3 c = 0;
4 else
5 c = a - b;

我了个去c语言中都这么麻烦的吗。2.5 有时候可能并不是2.5 由于计算机底层cpu运算的一些奇奇怪怪的玄机 我们也懒得去管。总之就算2.5 有可能实际是2.49999999999999999999999999

包括javascript 很多都有数制问题。

这段代码的问题在c# c 中都存在 并且float的标准都是遵循统一的规范 IEEE754 的(c#的二进制在c中解析的结果一样

1 float test = 0.1f;
2 if (test == (1 - 0.9))
3 {
4 Console.WriteLine("正常");
5 }
6 else
7 {
8 Console.WriteLine("what!!!");
9 }

聪明如你,看了上面的相信你已经知道怎么解决了。c#里更加无脑 傻瓜化的用decimal就可以了。

 

c语言以及高级语言中的float到底是什么以及IEEE754的更多相关文章

  1. R语言作为BI中ETL的工具

    R语言作为BI中ETL的工具,增删改 R语言提供了强大的R_package与各种数据库进行数据交互. 外加其强大数据变换清洗函数,为ETL提供一条方便快捷的道路. RODBC ROracal RMys ...

  2. 第九章 C语言在嵌入式中的应用

    上章回顾 编码的规范和程序版式 版权管理和申明 头文件结构和作用 程序命名 程序注释和代码布局规范 assert断言函数的应用 与0或NULL值的比较 内存的分配和释放细节,避免内存泄露 常量特性 g ...

  3. 关于CSS中的float可能出现的小问题

    关于CSS中的float可能出现的小问题 前言:最近学习CSS的float所遇到点小问题,然后顺便分享给大家. 一.什么是CSS以及float (一) CSS概述 CSS是层叠样式表(英文全称:Cas ...

  4. 【Java面试题】15 String s="Hello"; s=s+“world!”;这两行代码执行后,原始的String对象中的内容到底变了没有?String与StringBuffer的超详细讲解!!!!!

    1.Java中哪些类是不能被继承的? 不能被继承的是那些用final关键字修饰的类.一般比较基本的类型或防止扩展类无意间破坏原来方法的实现的类型都应该是final的,在java中,System,Str ...

  5. Java中的float、double计算精度问题

    java中的float.double计算存在精度问题,这不仅仅在java会出现,在其他语言中也会存在,其原因是出在IEEE 754标准上. 而java对此提供了一个用于浮点型计算的类——BigDeci ...

  6. Java中的String到底占用多大的内存空间?你所了解的可能都是错误的!!

    写在前面 最近小伙伴加群时,我总是问一个问题:Java中的String类占用多大的内存空间?很多小伙伴的回答着实让我哭笑不得,有说不占空间的,有说1个字节的,有说2个字节的,有说3个字节的,有说不知道 ...

  7. C语言调试过程中duplicate symbol错误分析

    说明:在我们调试C语言的过程中,经常会遇到duplicate symbol错误(在Mac平台下利用Xcode集成开发环境).如下图: 一.简单分析一下C语言程序的开发步骤. 由上图我们可以看出C语言由 ...

  8. C语言初学者代码中的常见错误与瑕疵(23)

    见:C语言初学者代码中的常见错误与瑕疵(23)

  9. 用C语言把双向链表中的两个结点交换位置,考虑各种边界问题。

    用C语言把双向链表中的两个结点交换位置,考虑各种边界问题. [参考] http://blog.csdn.net/silangquan/article/details/18051675

  10. c语言头文件中定义全局变量的问题

    c语言头文件中定义全局变量的问题 (转http://www.cnblogs.com/Sorean/) 先说一下,全局变量只能定义在 函数里面,任意函数,其他函数在使用的时候用extern声明.千万不要 ...

随机推荐

  1. VMware宿主机访问虚拟机的Web服务

    VMware宿主机访问虚拟机的Web服务,主要就是宿主机可以通过IP能够访问到虚拟机. 可以尝试使用以下步骤. 1.关闭虚拟机,把网络连接方式修改成桥接方式. 2.打开虚拟机后,把虚拟机的防火墙关闭. ...

  2. 18V降压3.3V,15V降压3.3V的降压IC和LDO芯片方案

    在 18V 和 15V 输入中,我们需要给其他电源电路提高供电,有的电路的供电电压在 5V,或者是 3.3V 时, 我们就需要使用降压芯片来组建一个降压电路来给后面的的电路,提供稳定的,持续的 3.3 ...

  3. Springboot优雅进行字段检验

    Springboot优雅进行字段检验 1.Controller VS Service 推荐与业务无关的放在controller层中进行校验,而与业务相关的放在service层中校验. 2.常用校验工具 ...

  4. MIsc writeup

    1. 杂项 图片里面有什么 ,附件为一张图片 通过Binwalk查看发现有压缩包,通过foremost分离一下. 打开输出文件,发现里面有两个图片. 00000000.png是原图,00000722. ...

  5. MyBatis详解(二)

    前言 本篇幅是继 MyBatis详解(一)的下半部分. MyBatis执行Sql的流程分析 [1]基于前面已经将XML文件进行build解析了并且返回了SqlSessionFactory [1.1]那 ...

  6. SpringBoot向Excel模板中写入数据并下载 (无需获取file对象及模板绝对路径)

    之前用获取模板路径的方式测试没问题打包后就有问题了 莫名出现一个! 找了很多教程尝试无果 最终使用下面这个方式 无需获取file对象以及模板路径的方式进行写入下载 (那个设置浏览器编码没有测试不知道能 ...

  7. Linux命令第三部分

    一.命令 1.mv命令 ·不更改文件路径 改名 ·更改文件路径 剪切 mv  [选项]  源文件或目录   目标文件或目录 2.which 查找命令.文件存放目录 搜索范围由环境变量PATH决定 3. ...

  8. 使用.NET开发搭建OpenAI模型的中间服务端

    前言:前不久微信上大家玩ChatGPT聊天机器人玩的不亦乐乎:不过随着ChatGPT被封杀,所以用微信聊天机器人有可能导致封号的风险.那如果自己不想每次都去OpenAI官网上进行对话[PS:官网上面聊 ...

  9. Centos7下vim最新版本安装

    一直以来用的都是vim,因为之前都是系统自带的vim没有研究过怎么自己安装,今天趁着刚装完新系统,顺便装下vim. 同样vim也有两种安装方法: 一.yum安装,centos下安装软件最简单的方法了, ...

  10. Linux基础:ssh与scp

    登陆 登陆服务器 ssh user@hostname user: 用户名 hostname :IP地址或域名 第一次登陆会提示 The authenticity of host '123.57.47. ...