嵌入式编程中的浮点数处理

什么是浮点数

浮点数是指一种既包含小数又包含整数的数据类型。

浮点型变量分类

浮点型分为单精度(float型)和双精度(double型)。浮点型变量使用定义。如下:

float a; // 声明单精度浮点型变量
double b; // 声明双精度浮点型变量

单精度(float型)与双精度(double型)的区别

float 单精度浮点数内存中占 4 个字节,用 32 位二进制描述。

double 双精度浮点数内存中占 8个字节,用 64 位二进制描述。

Float 有效数字6-7位,数值范围:-3.4*10(-38)~3.4*10(38)。

Double 有效数字15-16位,数值范围:-1.7*10(-308)~1.7*10(308)。

简单来说就是Float类型可以保存的数据最多为小数点后6位小数。比如说保存3.1415926这个数据只能保存下来的是3.141593。double类型也是如此最多可以保存小数点后15位小数。

float 优点:省内存,运算速度快。

float 缺点:精度低。

double 优点:精度高。

double 缺点:占内存,运算速度慢。

在嵌入式编程的时候一般都是以省内存,加快运算速度优先。所以在选择使用浮点型时float就可以满足大部分需求,当需要高精度的时候必须使用double进行运算。

浮点型数据在嵌入式中的使用

在嵌入式数据通讯过程中一般是以字节的形式进行数据交互。要想传输一个浮点数需要把浮点数转换为字节的形式。

MCU不能直接保存浮点型数据,能直接保存的是0或1。把数据保存到MCU中比较常用的保存方法就是以字节的方式把数据写到MCU的存储模块中去。所以说要保存一个浮点型数据到MCU前需要先把浮点型数据转换成字节的形式。下面就来说一说浮点型转换为字节类型的一些方法:

方法一

强制转换,就是把浮点型数据直接赋值到整型的方法,例子如下:

unsigned char a;
float b=3.14;
a=(unsigned char)b; //直接赋值有时候编译会报错所以需要在前面加强制转换(unsigned char)。

你觉得现在a等于多少?运行结果a=3。强制转换的缺点就是直接把小数位忽略掉保留整数。

要想把小数保留下来可以先把b乘以100,b=314再强制转换到a,这时a=314,再把a除以100赋值到浮点类型还原到原来的浮点数。强制转换到a后a=314吗?答案是a!=314。为什么?因为unsigned char是一个字节即(1111 1111)八位。值得范围为0~255,314>255赋值无效。正确的需要大a的定位类型改为数组使用两个字节保存以方便写入芯片,更改为:

unsigned char a[2]; //定义2个元素的数组,代表高位和低位
float b=3.14;
unsigned short int c=(unsigned short int)b*100; //3.14乘以100后强制转换到c,(unsigned short int)表示16位的无符号整型。通常为uint16_t这样写法。
a[0]=c>>8; //或者a[0]=c/256。>>8,相当于c除以256取整,所以a[0]=1;
a[1]=c; //c=314,赋值到a[1]中因为a[1]为unsigned char类型最大为255,所以当值大于255就会从零开始,所以a[1]最终等于58。类似于十进制的个位十位。还原数据就是一个相反的过程就是a[0]*256+a[1]或者a[0]<<8+a[1]。

方法二

使用打印函数转换为字符串存到字节数组。实例如下:

char pdata[10];
float b=3.14;
sprintf(pdata,"%.2f",b); //打印到文件函数,就是把浮点型b打印到pdata字节数组。
/*
以16进制的形式打印pdata的元素,3.14变成了33 2e 31 34,其实就是3.14本来是数字的变成了字符,然后以ascll码表对应的十六进制值保存到字节数组中来。
把字符串转换为浮点型数据的方法可以使用C库函数atof()进行转换。使用方法;
*/
#include 
char ch[]="3.14";
float fa=atof(str);

方法三

基于IEEE754格式的浮点数据方法,这是一种标准的浮点格式方法。单精度浮点型以4字节保存,双精度以8字节保存。转换相关方法如下。

单精度浮点数(float)

//浮点数转字节数组
void floatbytes(float data, unsigned char bytes[])
{
    unsigned char i;
    char* p = (char*)&data; //将float类型的指针强制转换为unsigned char型
      for(i=0; i<4; i++)
      {
      bytes[i] = *p++; //将相应地址中的数据保存到unsigned char数组中
      }
}
//字节数组转浮点数
float bytesfloat(unsigned char bytes[])
{
  float data = *((float *)bytes); //直接将unsigned char型的指针强制转换为double类型
  return data;
}

双精度浮点数(double )

//浮点型double转字节数组
void double2bytes(double data, unsigned char bytes[])
{
  int i;
  char* p = (char*)&data; //将double类型的指针强制转换为unsigned char型
    for(i=0; i<8; i++)
    {
    bytes[i] = *p++; //将相应地址中的数据保存到unsigned char数组中
    }
}
//字节数组转双精度浮点型数据double(小端模式)
double bytes2double(unsigned char bytes[])
{
      double data = *((double *)bytes); //直接将unsigned char型的指针强制转换为double类型
      return data;
}

有空可以验证一下这种方式的转换。以单精度为例使用方法如下:

float a=3.14;
unsigned char fch[4];
Floatbytes(a,fch);

你还知道其他的相关转换方法吗?欢迎留言。

每一种方法都有其优点和缺点,上面所说到的三种方法方法一相对来说比较容易理解和使用也不需要占用很多个字节,但是不太灵活。方法二转换成数组比较简单但是占用多少个字节不定,小数位保存越多占用字节越多,多个数据一起的时候稍微会麻烦一点。方法三,标准的保存方式,理解起来比较复杂一点。但是能够很好地把浮点数保存下来也无需处理符号位。对于比C语言更复杂一点的编程语言都有现有的转换函数调用。

到底那种方法好这就需要自己去验证了。

ES6里Class的Extends继承原理

Class的方法都加在原型链上

JS的原型链继承的本质是根据__proto__一层一层往上找

继承的时候只需要把子类的原型对象prototype里的__proto__属性指向父类的prototype即可

这就好理解Extends在干嘛了

    class A {
        // constructor也是定义在A的原型链上
        constructor(x,y){
            this.x = x;
            this.y = y;
        }
        // 直接加在了function A 的原型链式
        one(){
            return 1;
        }
    }
    
    class B extends A{
        constructor(){
            //相当于A.prototype.constructor.call(this)  
            super(1,2)
        }
    }
    var x = new B()
    console.log(x)

继承的时候extends干了什么

extends在实现继承方面,本质上也是原型链继承,该方法实现了两步原型链继承

大多数浏览器的 ES5 实现之中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。

Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

第一步是将类的原型对象(prototype)里的__proto__指向父类的原型对象:

B.prototype = Object.create(A.prototype, {constructor:{value: B}})

即将B.prototype.__proto__ =A.prototype

第二步是将类的__proto__指向父类:

(子类是父类构建出的函数对象,需要指定对象的__proto__)

Object.setPrototypeOf(B, A);

即将B.__proto__ =A (B由A构造而来)

最后需要继承构造函数里的属性和方法

内部写的有点绕,但最后还是通过apply(this, arguments)来继承的

(function(_A) {
    //继承原型对象上的属性和方法
    _inherits(B, _A);
    
    function B() {
      _classCallCheck(this, B);
      //继承构造函数中的实例属性和方法
      return _possibleConstructorReturn(
        this,
        _getPrototypeOf(B).apply(this, arguments)
      );
    }
    return B;
  })(A);

babeljs对extends语法糖的部分编译结果

function _inherits(subClass, superClass) {
    //对superClass进行类型判断
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function");
  }
  //子类的prototype继承父类的prototype
  //也就是说执行后 subClass.prototype.__proto__ === superClass.prototype; 这条语句为true
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: { 
        value: subClass, 
        writable: true, 
        configurable: true 
    }
  });
  //子类是父类构建出的函数对象,需要指定对象的__proto__
  if (superClass) _setPrototypeOf(subClass, superClass);
} 

联系寄生组合继承(call+寄生式封装)

寄生组合式继承原理:

使用借用构造函数(call)来继承父类this声明的属性/方法通过寄生式封装函数设置父类prototype为子类prototype的原型来继承父类的prototype声明的属性/方法。

可以发现ES6类的继承其实就是基于寄生组合继承来实现的

function inheritPrototype(subType, superType){
    //原型式继承:浅拷贝superType.prototype对象作为superType.prototype为新对象的原型
    // 内部会自带_proto_指向:prototype.\_\_proto\_\_ = superType.prototype;
    var prototype = Object.create(superType.prototype); 
    // subType.prototype.\_\_proto\_\_ = superType.prototype;
    subType.prototype = prototype;               // 将子类的原型替换为这个原型
    prototype.constructor = subType;             // 修正原型的构造函数
    
}
function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name, age){
    SuperType.call(this, name);
    this.age = age;
}
// 核心:因为是对父类原型的复制,所以不包含父类的构造函数,也就不会调用两次父类的构造函数造成浪费
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
    alert(this.age);
}

本站内容来自用户投稿,如果侵犯了您的权利,请与我们联系删除。联系邮箱:835971066@qq.com

本文链接:http://news.xiuzhanwang.com/post/2067.html

发表评论

评论列表

还没有评论,快来说点什么吧~

友情链接: