参考了如下连接, 并修正了其中的一些错误。
html
https://www.cnblogs.com/FlyingBread/archive/2009/02/15/660206.htmlios
一个在线转化工具c++
http://www.binaryconvert.com/convert_float.html架构
1 浮点数的表示
IEEE754用下面的格式来表示浮点数app
S | P | M |
其中S是符号位,P是阶码,M是尾数
单精度浮点数是32位(即4字节)的,双精度浮点数是64位(即8字节)的。二者的S,P,M所占的位数以及表示方法见下图:ide
以单精度浮点数为例,能够获得其二进制的表示格式以下函数
S(第31位) | P(30位到23位) | M(22位到0位) |
其中S是符号位,只有0和1,分别表示正负;P是阶码,一般使用移码表示(移码和补码只有符号位相反,其他都同样。对于正数而言,原码,反码和补码都同样;对于负数而言,补码就是其绝对值的原码所有取反,而后加1)
为了简单起见,本文都只讨论单精度浮点数,双精度浮点数也是用同样的方式存储和表示的。
2 浮点数的表示约定
单精度浮点数和双精度浮点数都是用IEEE754标准定义的,其中有一些特殊约定。
(1) 当P = 0, M = 0时,表示0。
(2) 当P = 255, M = 0时,表示无穷大,用符号位来肯定是正无穷大仍是负无穷大。
(3) 当P = 255, M != 0时,表示NaN(Not a Number,不是一个数)。工具
e为8(单精度)或者13(双精度)编码
规约形式的浮点数spa
若是浮点数中指数部分的编码值在 之间,且在科学表示法的表示方式下,小数 (fraction) 部分最高有效位(即整数字)是1,那么这个浮点数将被称为规约形式的浮点数。“规约”是指用惟一肯定的浮点形式去表示一个值。因为这种表示下的尾数有一位隐含的二进制有效数字,为了与二进制科学计数法的尾数(mantissa)相区别,IEEE754称之为有效数(significant)。
举例来讲,双精度 (64-bit) 的规约形式浮点数在指数偏移值的值域为
(11-bit) 到
,在小数部分则是
到
(52-bit)。
非规约形式的浮点数
若是浮点数的指数部分的编码值是0,小数部分非零,那么这个浮点数将被称为非规约形式的浮点数。通常是某个数字至关接近零时才会使用非规约型式来表示。 IEEE 754标准规定:非规约形式的浮点数的指数偏移值比规约形式的浮点数的指数偏移值小1。例如,最小的规约形式的单精度浮点数的指数部分编码值为1,指数的实际值为-126;而非规约的单精度浮点数的指数域编码值为0,对应的指数实际值也是-126而不是-127。实际上非规约形式的浮点数仍然是有效可使用的,只是它们的绝对值已经小于全部的规约浮点数的绝对值;即全部的非规约浮点数比规约浮点数更接近0。规约浮点数的尾数大于等于1且小于2,而非规约浮点数的尾数小于1且大于0。
C++中,单精度和双精度浮点数的一些特殊常量值以下。一般是在float.h里面定义的。
那么这些值是如何求出来的呢?
例如FLT_MAX, 根据上面的约定,咱们能够知道阶码P的最大值是11111110(这个值是254,由于255用于特殊的约定,那么对于能够精确表示的数来讲,254就是最大的阶码了)。尾数的最大值是11111111111111111111111。
那么这个最大值就是:0 11111110 11111111111111111111111。
也就是 2(254-127) * (1.11111111111111111111111)2 = 2127 * (1+1-2-23) = 3.40282346638529E+38
3 浮点数的精度问题
浮点数以有限的32bit长度来反映无限的实数集合,所以大多数状况下都是一个近似值。同时,对于浮点数的运算还同时伴有偏差扩散现象。特定精度下看似相等的两个浮点数可能并不相等,由于它们的最小有效位数不一样。
因为浮点数可能没法精确近似于十进制数,若是使用十进制数,则使用浮点数的数学或比较运算可能不会产生相同的结果。
若是涉及浮点数,值可能不往返。值的往返是指,某个运算将原始浮点数转换为另外一种格式,而反向运算又将转换后的格式转换回浮点数,且最终浮点数与原始浮点数相等。因为一个或多个最低有效位可能在转换中丢失或更改,往返可能会失败。
单精和双精浮点数的有效数字分别是有存储的23和52个位,加上最左手边没有存储的第1个位,便是24和53个位。
由以上的计算,单精和双精浮点数能够保证7位和15位十进制有效数字。
4 将浮点数表示为二进制
4.1 无小数的浮点数转换成二进制表示
首先,咱们用一个不带小数的浮点数来讲明如何将一个浮点数转换成二进制表示。假设要转换的数据是45678.0f。
在处理这种不带小数的浮点数时,直接将整数部分转化为二进制表示:
1011_0010_0110_1110.0
而后将小数点向左移,一直移到离最高位只有1位,也就是 1.0110010011011100,一共移动了15位,咱们知道,左移位表示乘法,右移位表示除法。因此原数就等于这样:1.011001001101110 * ( 215 )。为了知足规格化的要求,高位的1能够省略。尾数的二进制就变成了:011001001101110。
最后在尾数的后面补0,一直到补够23位,就是:011_0010_0110_1110_0000_0000。
再回来看指数,根据前面的定义,P-127=15,那么P = 142,表示成二进制就是:10001110。
45678.0f这个数是正的,因此符号位是0,那么咱们按照前面讲的格式把它拼起来,就是:0 10001110 01100100110111000000000。
这就是45678.0f这个数的二进制表示,若是咱们要获得16进制的表示,很是简单,咱们只须要把这个二进制串4个一组,转换成16进制数就能够了。可是要注意的是x86架构的CPU都是Little Endian的(也就是低位字节在前,高位字节在后),因此在实际内存中该数字是按上面二进制串的倒序存储的。要知道CPU是否是little endian的也很容易。
BitConverter.IsLittleEndian;
4.2 含小数的浮点数表示为二进制
对于含小数的浮点数,会有精度的问题,下面举例说明。假设要转换的小数为123.456。
对于这种带小数的就须要把整数部和小数部分开处理。对于整数部分的处理再也不赘述,直接化成二进制为:01111011。小数部份的处理比较麻烦一些,咱们知道,使用二进制表示只有0和1,那么对于小数就只能用下面的方式来表示:
a1*2-1+a2*2-2+a3*2-3+......+an*2-n
其中a1等数能够是0或者1,从理论上将,使用这种表示方法能够表示一个有限的小数。可是尾数只能有23位,那么就必然会带来精度的问题。
在不少状况下,咱们只能近似地表示小数。来看0.456这个十进制纯小数,该如何表示成二进制呢?通常说来,咱们能够经过乘以2的方法来表示。
首先,把这个数字乘以2,小于1,因此第一位为0,而后再乘以2,大于1,因此第二位为1,将这个数字减去1,再乘以2,这样循环下去,直到这个数字等于0为止。
在不少状况下,咱们获得的二进制数字都大于23位,多于23位的就要舍去。舍入原则是0舍1入。经过这样的办法,咱们能够获得二进制表示:1111011.01110100101111001。
如今开始向左移小数点,一共移了6位,这时候尾数为:1.11101101110100101111001,阶码为6加上127得133,二进制表示为:10000101,那么总的二进制表示为:
0 10000101 11101101110100101111001
表示成十六进制是:42 F6 E9 79
因为CPU是Little Endian的,因此在内存中表示为:79 E9 F6 42。
4.3 将纯小数表示成二进制
对于纯小数转化为二进制来讲,必须先进行规格化。例如0.0456,咱们须要把它规格化,变为1.xxxx * (2n )的形式,要求得纯小数X对应的n可用下面的公式:
n = int( 1 + log 2X )
0.0456咱们能够表示为1.4592乘以以2为底的-5次方的幂,即1.4592 * ( 2-5 )。转化为这样形式后,再按照上面处理小数的方法处理,获得二进制表示
1. 01110101100011100010001
去掉第一个1,获得尾数
01110101100011100010001
阶码为:-5 + 127 = 122,二进制表示为
0 01111010 01110101100011100010001
最后转换成十六进制
11 C7 3A 3D
下面是用c++写的把string 串表示的ieee754格式单精度数转化为float类型数,以及把float类型单精度浮点数转化成ieee754表示的二进制串的函数。
// floatarith1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 //https://www.cnblogs.com/mikewolf2002/ #include "pch.h" #include <iostream> #include <cstdio> #include <string> #include <cmath> #include <vector> #include <algorithm> using namespace std; bool floatiszero(float d) { if (d >= -FLT_EPSILON && d <= FLT_EPSILON) return true; else return false; } bool doubleiszero(double d) { if (d >= -DBL_EPSILON && d <= DBL_EPSILON) return true; else return false; } //二进制串转化成整数,转化阶码 int pbstrtoint(string str, int n) { int i; int sum = 0; int bitnum = 1; for (i = 0; i < n; i++) { if (str[n - 1 - i] == '0') sum += 0 * bitnum; else if (str[n - 1 - i] == '1') sum += 1 * bitnum; bitnum = bitnum << 1; } return sum-127; } //二进制尾数转化成实数 float mbstrtofloat(string str, int n) { int i; double sum = 1.0; int bitnum = 2; float fbitnum; for (i = 0; i < n; i++) { fbitnum = 1.0 / bitnum; if (str[i] == '0') sum += 0 * fbitnum; else if (str[i] == '1') sum += 1 * fbitnum; bitnum = bitnum << 1; } return sum; } //ieee754表示的二进制串转化成浮点数 float bstrtofloat(string str, int n) { double sum=0; float sign = 1.0; float m; int p; if (n == 32) {//单精度浮点数 if (str[0] == '0') sign = 1.0; else if (str[0] == '1') sign = -1.0; p = pbstrtoint(str.substr(1, 8), 8); m = mbstrtofloat(str.substr(9,23), 23); sum = sign * pow(2.0, p*1.0) *m; } else if (n == 64) {//双精度浮点数 } return sum; } //整数to二进制str string inttobstr(int n) { string str = ""; int m = 0; while (n > 0) { m = n % 2; if (m == 1) str.append("1"); else str.append("0"); n = n >> 1; } reverse(str.begin(), str.end()); return str; } // 整数to二进制str,only 8 bits binary,高位补0 string inttobstr8(int n) { string str = ""; int m = 0; int t = 0; if (n > 255) return ""; while (n > 0) { m = n % 2; if (m == 1) str.append("1"); else str.append("0"); n = n >> 1; t++; } if (t < 8) str.append(8 - t, '0'); reverse(str.begin(), str.end()); return str; } //尾数to二进制字符串 string mfloattostr(float f) { string str=""; int m = 0; do { f = f * 2.0; if (f >= 1.0) { str.append("1"); f = f - 1.0; } else str.append("0"); m++; } while ((!floatiszero(f)) | (m<23)); return str; } //a为整数部分,b为小数部分 string floattobstr(float f) { string str = "00000000000000000000000000000000"; string str1 = ""; string str2 = ""; string str3 = ""; string str4 = ""; int i; // int j; if (f < 0) str[0] = '1'; else str[0] = '0'; f = abs(f); //取绝对值 int a; float b; int pcode = 0;//移码 a = floor(f);//向下取整,整数部分 b = f - a; //小数部分 str1 = inttobstr(a); str2 = mfloattostr(b); if (str1.length() > 0) {//整数部分小数部分都有 pcode = 127 + str1.length() - 1;//such as 110111.110, 此时pcode=127+5,小数点位左移 str3 = str1 + str2; } else {//只有小数部分,阶码为0或负值,找str2最左边的第一个1. for (i = 0; i < str2.length(); i++) { if (str2[i] == '1') break; } i = i + 1; //skip first 1 pcode = 127 - i; str3 = str2.substr(i, str2.length() - i); } str4 = inttobstr8(pcode); for (i = 0; i < 8; i++) str[i + 1] = str4[i]; for (i = 0; i < (str3.length()) && i < 23; i++) str[9 + i] = str3[i]; // for (j = i; j < 23; j++) // str[9 + j] = '0'; //fill 0 in the end return str; } int main() { string str1; //str1 = "01000010111101101110100101111001"; str1 = "00111101001110101100011100010001"; //01000001000100000000000000000000 //01000111001100100110111000000000 //0 10000101 1110110111010010111100 //0 01111010 11101011000111000100010 int n; float f; n = pbstrtoint("10001110", 8); printf("n is %d\n", n); f = mbstrtofloat("01100100110111000000000", 23); printf("n is %d,f is %23.22f\n", n,f); f = bstrtofloat(str1, 32); printf("%s is %23.22f\n", str1.c_str(), f); str1 = inttobstr(19); printf("19 is %s\n", str1.c_str()); str1 = floattobstr(0.0456); printf("0.0456 is %s\n", str1.c_str()); //简单方法在在c++中显示浮点数的ieee754表示, num的值即为9.0的ieee754 表示 int num = 9; /* num是整型变量,设为9 */ float* pFloat = (float*)# /* pFloat表示num的内存地址,可是设为浮点数 */ printf("num的值为:%d\n", num); /* 显示num的整型值 */ printf("*pFloat的值为:%f\n", *pFloat); /* 显示num的浮点值 */ *pFloat = 9.0; /* 将num的值改成浮点数 */ printf("num的值为:%d\n", num); /* 显示num的整型值 */ printf("*pFloat的值为:%f\n", *pFloat); /* 显示num的浮点值 */ return 0; }