C#中32位浮點數Float(Real)一步步按位Bit進行分析

我們都知道單精度浮點數(Single,float,Real)由32位0或1組成,它具體是如何來的。

浮點數的32位N=1符號位(Sign)+8指數位(Exponent)+23尾數部分(Mantissa)

  • 符號位(Sign) : 0代表正,1代表為負【占1位】
  • 指數位(Exponent)::用於存儲科學計數法中的指數數據,並且采用移位存儲【占8位】
  • 尾數部分(Mantissa):尾數部分【占23位】
  • 單精度float:N共32位,其中S占1位,E占8位,M占23位。因此小數點後最多精確到23/4=6位 。

C#代碼示例如下

using System;
using System.Collections.Generic;
using System.Linq;
namespace ConverterAndPrecisionDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            //參考博客:https://blog.csdn.net/zhengyanan815/article/details/78550073
            //小數點後面:4個位占用一個數字【十進制9就是1001】
            //符號位(Sign) : 0代表正,1代表為負【占1位】
            //指數位(Exponent):用於存儲科學計數法中的指數數據,並且采用移位存儲【占8位】
            //尾數部分(Mantissa):尾數部分【占23位】
            //單精度float:N共32位,其中S占1位,E占8位,M占23位。因此小數點後最多精確到23/4=6位 
            //雙精度double:N共32位,其中S占1位,E占11位,M占52位。因此小數點後最多精確到52/4=13位 
            //十進制小數的二進制表示:【法則--整數部分:除基取餘,逆序拼接。小數部分:乘基取整,順序拼接】
            //整數部分:除以2,取出餘數,商繼續除以2,直到得到0為止,將取出的餘數逆序。可以使用棧Stack
            //小數部分:乘以2,然後取出整數部分,將剩下的小數部分繼續乘以2,然後再取整數部分,一直取到小數部分為零為止。如果永遠不為零,則按要求保留足夠位數的小數,最後一位做0舍1入。將取出的整數順序排列。可以使用隊列Queue
            float f = 123456.8125F;
            byte[] buffer = BitConverter.GetBytes(f);
            Console.WriteLine("打印浮點數對應的4個字節:");
            Console.WriteLine(string.Join(",", buffer));
            Console.WriteLine($"【使用函數】{123456}對應的二進制:{ Convert.ToString(123456, 2)}");
            int num = 123456;
            Stack<int> stack = new Stack<int>();
            while (num != 0)
            {
                int cur = num % 2;
                stack.Push(cur);
                num = num / 2;
            }
            Console.WriteLine($"【使用堆棧】{123456}對應的二進制:{ string.Join("", stack)}");
            int scale = 10;
            int index = 0;
            double d = 0.8125;
            Queue<int> queue = new Queue<int>();
            while (index < scale)
            {
                int cur = (int)(d * 2);
                queue.Enqueue(cur);
                d = d * 2 - cur;
                if (d == 0)
                {
                    break;
                }
                index++;
            }
            Console.WriteLine($"{0.8125}對應的二進制:{ string.Join("", queue)}");
            string binaryDisplay = string.Join("", stack) + "." + string.Join("", queue);
            Console.WriteLine($"{123456.8125}對應的二進制為{binaryDisplay}");
            int dotIndex = binaryDisplay.IndexOf('.');
            //移除小數點後將小數點插入索引1的位置【即:小數點移動到索引1的位置】
            string scienceDisplay = binaryDisplay.Remove(dotIndex, 1).Insert(1, ".");
            Console.WriteLine($"小數{123456.8125}對應的二進制科學計數為{scienceDisplay}×(2的{dotIndex - 1}次方)");
            string sign = (f > 0 ? "0" : "1");//符號位占用1位
            Console.WriteLine($"符號位S:正數為0,負數為1。符號位是:{sign}");
            string exponent = Convert.ToString(127 + (dotIndex - 1), 2).PadLeft(8, '0');//指數位占用8位
            Console.WriteLine($"指數位E:123456最高位為2的{dotIndex - 1}次方,指數為{dotIndex - 1},因此指數位E的十進制值為【127+{dotIndex - 1}={127 + dotIndex - 1}】");
            //尾數部分:去除scienceDisplay開始的"1.",也就是字符串從索引2開始。並湊夠23位
            string mantissa = scienceDisplay.Substring(2).PadRight(23, '0');//尾數位占用23位
            Console.WriteLine($"尾數位M:尾數部分M需要湊夠23位。為【{mantissa}】");
            string joinBits = sign + exponent + mantissa;//符號位占用1位+指數位占用8位+尾數位占用23位=32位
            byte[] bufferJoin = new byte[4];
            for (int i = 0; i < 4; i++)
            {
                bufferJoin[i] = Convert.ToByte(joinBits.Substring(8 * i, 8), 2);
            }
            Console.WriteLine("重新拼接形成的32位浮點數,對應的4個字節為:");
            Console.WriteLine(string.Join(",", bufferJoin));
            byte[] reverseBuffer = bufferJoin.Reverse().ToArray();
            Console.WriteLine("反轉數組bufferJoin的順序:重新打印我們會發現與浮點數原始的字節完全一致。註意:C#是低字節在前");
            Console.WriteLine(string.Join(",", reverseBuffer));
            Console.ReadLine();
        }
    }
}

程序運行結果

關於32位浮點數的一些理解

1、定點的缺點

對於一個系統可能出現一些特別大的數和特別小的數,如果用定點表示就會很僵硬,位數一定就不能同時表達特別大的數和特別小的數。

2、對於定點123.625

用科學計數法的方式可以寫成1.23625*10^2,也可以寫成12.625*10^1或1.111011101*2^6。。。。。為瞭規范,IEEE就規定瞭32位浮點的格式如下

https://img2018.cnblogs.com/blog/1070689/201903/1070689-20190310165139947-1347641585.png

3、翻譯一下

(1)最高位是符號位,“0”代表正,“1”代表負。

(2)接下來的8位是指數位,8位可表示整數的范圍是0-255,考慮指數可以是負的,IEEE規定在上面的范圍減去127,並將-127(全0)和128(全1)用做特殊值處理,所以指數的位的范圍是(-127,128)。

(3)最低的23位是小數位(尾數位),正常是可以表示23位的范圍,但是IEEE規定小數點左側必須為1,右側位數不夠補0。這樣可以就可以省略1,可以用23位來表示24位。

eg. 1.111011101*2^6中,小數位是111011101+補14個0

4、定點轉浮點實例:123.625用32位浮點表示

科學計數法=1.111011101*26(整數部分:123=01111011b,小數部分:0.625=0.101b,整數部分除2取餘,倒序排列,高位補零;小數部分乘2取整,順序排列)

符號位:0

指數位:6+127=10000101

小數位:11101110100000000000000

即:01000010111101110100000000000000=0x42F74000

5、驗證

6、浮點轉定點實例

42F74000=01000010111101110100000000000000,拆分為符號位、指數位、小數位。

(1)符號位:0

(2)指數位:10000101=133,實際指數=133-127=6

(3)小數位:11101110100000000000000去掉後面的0、前面補1為1. 111011101

即科學計數法表示為1. 111011101*26=(2^0+2^-1+2^-2+2^-3+2^-5+2^-6+2^-7+2^-9)*2^6=123.625。

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。 

推薦閱讀: