浮點數的表示和運算

參考瞭如下連接, 並修正了其中的一些錯誤。
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

image

以單精度浮點數爲例,能夠獲得其二進制的表示格式以下函數

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(雙精度)編碼

image

image

規約形式的浮點數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*)&num; /* 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;

}
View Code


5 浮點數的數學運算
5.1 浮點數的加減法

設兩個浮點數 X=Mx*2Ex ,Y=My*2
Ey

實現X±Y要用以下5步完成:
(1)對階操做:小階向大階看齊
(2)進行尾數加減運算
(3)規格化處理:尾數進行運算的結果必須變成規格化的浮點數,對於雙符號位(就是使用00表示正數,11表示負數,01表示上溢出,10表示下溢出)的補碼尾數來講,就必須是
001×××…×× 或110×××…××的形式
若不符合上述形式要進行左規或右規處理。
(4)舍入操做:在執行對階或右規操做時經常使用「0」舍「1」入法將右移出去的尾數數值進行舍入,以確保精度。
(5)判結果的正確性:即檢查階碼是否溢出
若階碼下溢(移碼錶示是00…0),要置結果爲機器0;
若階碼上溢(超過了階碼錶示的最大值)置溢出標誌。
如今用一個具體的例子來講明上面的5個步驟
例題:假定X=0 .0110011*211,Y=0.1101101*2-10(此處的數均爲二進制), 計算X+Y;
首先,咱們要把這兩個數變成2進製表示,對於浮點數來講,階碼一般用移碼錶示,而尾數一般用補碼錶示。
要注意的是-10的移碼是00110
        [X]: 0        1 010  1100110
        [Y]: 0        0 110  1101101
                   符號位 階碼   尾數
(1)求階差:│ΔE│=|1010-0110|=0100
(2)對階:Y的階碼小,Y的尾數右移4位
        [Y]變爲 0 1 010 0000110 1101暫時保存
(3)尾數相加,採用雙符號位的補碼運算
     00 1100110
   +00 0000110
     00 1101100
(4)規格化:知足規格化要求
(5)舍入處理,採用0舍1入法處理
故最終運算結果的浮點數格式爲: 0 1 010 1101101
即X+Y=+0. 1101101*210
5.2 浮點數的乘除法
(1)階碼運算:階碼求和(乘法)或階碼求差(除法)
    即  [Ex+Ey]移= [Ex]移+ [Ey]補
          [Ex-Ey]移= [Ex]移+  [-Ey]補
(2)浮點數的尾數處理:浮點數中尾數乘除法運算結果要進行舍入處理
例題:X=0 .0110011*211,Y=0.1101101*2-10  求X*Y
解:[X]: 0 1 010 1100110
        [Y]: 0 0 110 1101101
(1)階碼相加
[Ex+Ey]移=[Ex]移+[Ey]補=1 010+1 110=1 000
1 000爲移碼錶示的0
(2)原碼尾數相乘的結果爲:
0 10101101101110
(3)規格化處理:已知足規格化要求,不需左規,尾數不變,階碼不變。
(4)舍入處理:按舍入規則,加1進行修正
因此 X※Y= 0.1010111*20
相關文章
相關標籤/搜索