Cortex-M4 FPU(Floating Point Unit) 雜記:

浮點數的IEEE 754標準

簡而言之,該標準採用了以2爲基數的科學記數法記錄實數,並將記數範圍上的4個邊界值定義爲不同的特殊值

                                                    

                       
上述元素之間的關係爲:

符號域S記錄了浮點數的符號;階碼域P -偏移量Bias構成了指數(Exponent)尾數域M(Mantissa)則僅記錄了實際的尾數的小數部分(Fraction),而其整數部分由於規範化表達而默認爲1,爲節約空間故不存儲;而對於基數(Base),都默認爲2。

注:尾數有時候也稱爲有效數字(Significand)

 

單精度浮點數一般表達爲例作進一步的解釋:

符號位S —— 爲0時表示正數,爲1時表示負數;

階碼域P —— 填入0000 0001 ~ 1111 1110(1~254)範圍內的值,通過表達式減去Bias(127)後就表達了數值範圍爲 -126 ~ +127的指數,可見P的正常取值範圍是去頭去尾的;

尾數域M —— 填入0000 0000 0000 0000 0000 0000 ~ 111 1111 1111 1111 1111 1111範圍內的值,表達了實際尾數的小數部分,實際尾數爲“1.M”;

偏移量Bias —— 該值固定爲127,是爲了方便表達負指數而引入的,相當於將指數取值範圍的原點移動到了階碼域P取值範圍的中位。

 

在某些特殊的浮點運算操作下,會造成特殊值,特殊值有:

  1. 當指數爲-127(對應P域全爲0),尾數≠0時,表示的是特殊值非規範化數”
  2. 當指數爲-127,尾數=0時,表示的是特殊值“0”
  3. 當指數爲128(對應P域全爲1),尾數≠0時,表示的是標識符“NaN(Not a Number)”
  4. 當指數爲128,尾數=0時,表示的是特殊值“∞”

上述特殊值的一般形式可整理爲如下表格:

                                                  

 

> 非規範化數:

非規範數規範化數之間的主要區別在於:

規範化數尾數的整數部分默認爲“1”,所表達的完整尾數爲“1.M”;

非規範數尾數的的整數部分默認爲“0”,所表達的完整尾數爲“0.M”。

非規範數的引入是爲了應對兩個非常接近的浮點數相減的情況,其運算結果將非常接近0,以至於超出了規範化表達的指數範圍(如:運算1.0001*2-125-1.0000*2-125結果爲0.0001*2-125,規範化表達爲1.0000*2-129,超出了指數的最小值-126)。

此時,若粗暴地將此類結果歸0並繼續運算的話,在進一步除法運算(如用1.0*2-4除上述結果)時,將導致特殊值“∞”的出現。然而其實際結果卻爲1,是一個巨大的誤差。

爲此,IEEE標準引入了非規範數來解決上述問題,當運算出現指數<-126而尾數≠0的結果時,浮點數將自動轉換爲非規範表達:其尾數域的整數部分默認轉變爲0,指數部分保留爲-127(對應P域全爲0),而尾數的小數部分則可以直接表示。

如此一來,上述例子的運算結果可保留爲0.001*2-127。可見,非規範數的引入進一步拓展了IEEE標準對微小浮點數的表達能力,使其指數域取值範圍拓展爲[-150, 127](最接近0的值爲±0.0000 0000 0000 0000 0000 001*2-127),該範圍可完全覆蓋任意兩個相近浮點數(規範化或非規範)相減的結果

 

> NaN(Not a Number):

NaN用於處理計算中出現錯誤情況,如0.0÷0.0或求負數的平方根

要注意的是所有的NaN值都是無序的。數值比較操作符==<<=>和>=在任一操作數爲NaN時均返回False而操作符!=則當任一操作數爲NaN時返回True。

(轉載自:https://blog.csdn.net/tercel_zhang/article/details/52537726

 

> +0和-0:

一般情況下,+0和-0是等同的。

但是,若遇到以0爲除數的運算,用+0的結果將爲+∞,用-0的結果將爲-∞。這有可能引發錯誤,需謹慎處理。

 

IEEE浮點數舍入策略——“四捨六入五留雙”

以單精度浮點數爲例,其24位的尾數(隱藏1位)決定了其最大有效數字(二進制下)爲24個。當運算的結果出現了多於24位的有效數字時,必須對多出的有效數字進行舍入。舍入的核心目的即是儘可能使舍入後的結果與真實值誤差最小(儘可能減小舍入處理的影響)。

IEEE標準的舍入策略設計於應對以下3種情況:

  1. 當最低有效位的後一位是0時,類似於“四舍”,應向下舍入;
  2. 當最低有效位的後一位是1,且往後的數位不全爲0時,類似於“六入”,應向上舍入;
  3. 當最低有效位的後一位是1,且往後的數位全爲0時,舍入過程遵循“使得最低有效位爲0(又稱作向偶數舍入)”。

簡而言之,該策略就是十進制中的“四捨六入五留雙”!

 

IEEE浮點數的精度

實際上,計算機內以二進制表達浮點數就是在區間[0,1]內以“二分法”對十進制浮點數的小數部分進行逼近的“過程”。

舉個例子,十進制的浮點數0.1,表示爲具有8位有效數字位(尾數域爲7位)的二進制浮點數的過程爲:

整數部分= 0,二進制整數也爲0;

0.1*2 = 0.2,取整數部分0;

0.2*2 = 0.4,取整數部分0;

0.4*2 = 0.8,取整數部分0;

0.8*2 = 1.6,取整數部分1,餘0.6; 【第1個有效數字】

0.6*2 = 1.2,取整數部分1,餘0.2; 【第2個有效數字】

0.2*2 = 0.4,取整數部分0;             【第3個有效數字】

0.4*2 = 0.8,取整數部分0;             【第4個有效數字】

0.8*2 = 1.6,取整數部分1,餘0.6; 【第5個有效數字】

0.6*2 = 1.2,取整數部分1,餘0.2; 【第6個有效數字】

0.2*2 = 0.4,取整數部分0;             【第7個有效數字】

0.4*2 = 0.8,取整數部分0;             【第8個有效數字,舍入爲1】

0.8*2 = 1.6,取整數部分1,餘0.6; 【需要舍入的數字】

0.6*2 = 1.2,取整數部分1,餘0.2; 【舍入參考的數字】

結果爲:B0.00011001101

其逼近結果的規範化表達,對應十進制逼近結果爲0.10009765625。直觀地判斷,這個結果有11位有效數字,那麼是否就能認爲具有8位尾數的二進制浮點數就可以表達出具有11位有效數字的十進制浮點數呢?

然而並不是!考察有效數字的定義:

有效數字的定義——> 如果近似數的絕對誤差不超過它某位數字的半個單位,那麼從左到右,第一個不爲零的數字起,到這位數字止,每一位數字都稱爲有效數字。

可見對於近似數0.10009765625,與目標值0.1的差的絕對值爲0.00009765625小於0.0005(小數點後第3位的半個單位),因此,其有效數字位只有3位!這是通過直接計算轉換結果的誤差來計算轉換的有效數字位的方法。

此外,還可以通過浮點數的尾數來計算其轉換爲十進制後的有效數字位數,令尾數域全爲1,該二進制數所代表的最大十進制整數的位數就近似於其有效數字。如:上述8位尾數爲1111 1111時,表達的十進制數是255,有3位有效數字,因此8位尾數浮點數可表達的有效數字位就爲3位;同理,24位尾數浮點數可表達的有效數字位約爲8位。

根據上述描述可推測,IEEE浮點數可表達的十進制浮點數的範圍和精度如下:

                     

 

(圖片轉載自:https://blog.csdn.net/tercel_zhang/article/details/52537726

 

浮點數“尺子”比喻

進一步地,討論用上述8尾數位浮點數表達距離B0.00011001101最近的數,容易推測,應該是B0.00011001100和B0.00011001110,那麼我們來看一下這兩個數對應的十進制浮點數是多少呢?

                                                 

根據“四捨六入五留雙”的原則,只要在實數軸上落於微小區間內的小數,都會被歸納爲0.10009765625,正如0.1與0.10009765625的差爲0.00009765625小於2-12(0.000244140625),就被歸納到了0.10009765625的點上。同理,落在該區域內另外兩邊的小數也會被歸納到0.099609375和0.1005859375的點上。

更形象地說,計算機內的浮點數就是一把“尺子”,以之表達十進制實數就是在用這把“尺子”來量某一條直線的長度,並將該測量結果作爲該直線的真實長度來使用。如此一來,就不難理解二進制浮點數有效數字的含義了。

                         

 

該圖展示的是4尾數位2指數位浮點數“尺子”的上的刻度

(圖片轉載自:https://blog.csdn.net/dreamer2020/article/details/24158303

 

> 實際上,大部分的十進制浮點數都無法完全準確地轉換爲二進制浮點數

因此,涉及到浮點數運算的代碼需要格外謹慎地設計,應儘量避免運算過程中出現數值過小的數據,並在類似

If( 變量 == 0 ) {...;}

的判斷中,用一個誤差值ERROR來替代0,如替換爲:

If( 變量 >= - ERROR && 變量 <= ERROR ) {...;}

 

—————————————————————————分界線——————————————————————————

 

Cortex-M4的浮點運算支持

硬件支持單精度浮點運算,提供浮點運算指令集,單週期完成運算;

軟件支持雙精度浮點運算,使用C語言提供的Run-time Library函數,運算速度較慢,但是對於某些三角函數的運算又是必須的;

支持定點運算(不常用),數據的存儲和運算與整型數類似(增加移位操作),運算速度較快,但是可表達的數值較少。

 

Cortex-M4 FPU——FPv4-SP(Floating Point version 4 - Single Precision)

FPU作爲Cortex-M4的協處理器,其協處理編號爲#10和#11,可在System Control Block(SCB)的“協處理器訪問控制寄存器(CPACR)”中加以使能與權限設置。

與其他協處理器不同,ARM架構CPU通過Thumb-2指令集中的“協處理器訪問指令”來訪問其他協處理器,而通過FPU專用的“V指令”來訪問FPU。不過在Cortex-M4中,FPU是其唯一的協處理器。

 

> Cortex-M4的FPU屬於ARMv7-M處理器架構的一個拓展子集FPv4-SP,而該拓展子集又從屬於ARMv7-A和ARMv7-R處理器架構的拓展子集VFPv4-D16(VFP既Vector Floating Point),因此其指令集全都以“V”開頭,以區別於其他指令。

 

FPU與CPU流水線的關係如圖所示,可見兩者基本上是並行運行的:

 

                                          

FPU可完成單精度浮點數的硬件計算、與FPU寄存器相關的數據傳輸任務,以及以下數據類型的雙向轉換任務:

單精度浮點數 <----> 整型數

單精度浮點數 <----> 半精度浮點數

單精度浮點數 <----> 定點數

 

FPU的使用

使能方面:

用戶可以通過在CMSIS-Core文件中設置宏定義__FPU_USED的值爲1來使能FPU;也可以通過在Keil-MDKC上設置Code Generation的FPU選項來完成。此時,在編譯器生成的代碼的SystemInit()函數內就會出現相應的FPU使能指令(完成了寫CPACR)。

代碼的編譯:

對於Cortex-M4處理器,其FPU只能完成單精度浮點數的硬件運算,但是在應用程序中不可避免地需要進行一些雙精度浮點運算(或其他複雜的浮點運算),此時還是需要調用“C Run-time Library”的相關函數來完成。然而兩種實現過程所用到的指令集和寄存器空間都不一樣,切不可用相同的方式進行編譯。

換而言之,對不同類型的浮點數需要採用不同編譯策略,以實現系統效率的最優化。

Keil-MDK編譯器在編譯浮點運算有關的函數時,默認的ABI(Application Binary Interface)選項爲Auto,也就是ARM手冊中所述的“Soft ABI with FPU(-fpu = softvfp + fpv4-sp)”。

如下圖中的第二種ABI選項所示,在該模式下編譯不同的函數時,編譯器根據不同函數的ELF文件內記錄的函數屬性來決定是由軟件還是硬件來完成函數內的浮點運算。

  1. 當函數需要完成簡單浮點運算(單精度)時,採用Hard-float模式,編譯器將代碼直接編譯成發射給FPU執行;
  2. 當函數需要完成複雜浮點運算(雙精度)時,採用Soft-float模式,編譯器把浮點運算轉換成浮點運算的函數調用和庫函數調用,沒有FPU的指令調用,也沒有浮點寄存器的參數傳遞,浮點參數的傳遞也是通過ARM寄存器或者堆棧完成。

 

                                            

> ABI(Application Binary Interface)

用來將C語言源碼翻譯爲對應硬件平臺(如ARMv7-M/A/R、Cortex-M、x8086等)的指令集彙編語言的轉換協議!

 

> ELF(Executable and Linking Format)格式

可執行鏈接格式,該格式用於記錄目標文件(*.o、*.elf、*.axf等)的內容,告訴編譯器應該如何鏈接、加載及執行該應用程序(或函數)

ELF格式包含ELF文件頭、程序頭、節區(Section)以及節區頭部表,功能分別爲:

ELF文件頭用來描述整個文件的組織,例如數據的大小端格式,程序頭、節區頭在文件中的位置等

程序頭告訴系統如何加載程序,例如程序主體存儲在本文件的哪個位置,程序的大小,程序要加載到內存什麼地址等等。

節區是*.o文件的獨立數據區域,它包含提供給鏈接使用的大量信息,如指令Code、數據RO、RW、ZI-data、符號表函數、變量名、重定位信息等,例如每個由C語言定義的函數在*.o文件中都會有一個獨立的節區

節區頭包含了本文件節區的信息,如節區名稱、大小等等。

                            

 

舉例:

                                                    

 

(轉載自:https://blog.csdn.net/weixin_33795093/article/details/93637835

 

補充記錄一些KEIL-MDK開發的知識點

目標文件有3類,分別爲:

  1. 可重定位文件(Relocatable File),包含基礎代碼和數據,但它們都沒有指定絕對地址,因此它適合於與其他目標文件鏈接來創建可執行文件或者共享目標文件。 這種文件一般由編譯器根據源代碼生成。如:KEIL-MDK生成的*.o文件,Linux的*.o文件,Windows的*.obj文件等。
  2. 可執行文件(Executable File),它包含適合於執行的程序,它內部組織的代碼數據都有固定的地址(或相對於基地址的偏移),系統(在這裏討論的就是編譯器)可根據這些地址信息把程序加載到內存中執行。這種文件一般由鏈接器根據可重定位文件鏈接而成,它主要是組織各個可重定位文件,給它們的代碼及數據一一打上地址標號,固定其在程序內部的位置,鏈接後,程序內部各種代碼及數據段不可再重定位(即不能再參與鏈接器的鏈接)。如:例如KEIL-MDK的armlink生成的*.elf及*.axf文件,Linux的/bin/bash文件,Windows的*.exe文件等。
  3. 共享目標文件(Shared Object File),類似動態鏈接庫的文件,可以在程序執行的過程中繼續參與鏈接,加入到可執行文件之中。如:Linux的/lib/glibc-2.5.so,Windows的DLL等。> 對於一般的嵌入式系統應用應該不存在這種文件?

hex、bin及axf文件的區別與聯繫:

bin、hex及axf文件都包含了指令代碼,但它們的信息豐富程度是不一樣的。

bin文件是最直接的代碼映像,它記錄的內容就是要存儲到FLASH的二進制數據(機器碼本質上就是二進制數據),在FLASH中是什麼形式它就是什麼形式,沒有任何輔助信息,包括大小端格式也沒有,因此下載器需要有針對芯片FLASH平臺的輔助文件才能正常下載(一般下載器程序會有匹配的這些信息);

hex文件是一種使用十六進制符號表示的代碼記錄,記錄了代碼應該存儲到FLASH的哪個地址,下載器可以根據這些信息輔助下載;

axf文件在前文已經解釋,它不僅包含代碼數據,還包含了工程的各種信息,因此它也是三個文件中最大的。

(轉載自:https://blog.csdn.net/weixin_33795093/article/details/93637835

MicroLIB:

MicroLIB是缺省 C 庫的備選庫。 它用於必須在極少量內存環境下運行的深層嵌入式應用程序。 這些應用程序不在操作系統中運行。MicroLIB 進行了高度優化以使代碼變得很小。它的功能比缺省 C 庫少,並且根本不具備某些 ISO C 特性。某些庫函數的運行速度也比較慢,例如,memcpy( )。MicroLIB不會嘗試成爲符合標準的 ISO C 庫。 

選上“Use MicroLIB”這是KEIL-MDK自帶的一個簡易的庫,例如你用printf()函數的時候,就會從串口1輸出字符串,直接默認定向到串口1。

(轉載自:https://blog.csdn.net/kelsey11/article/details/51246636

半主機模式(Semi-Host):

半主機是用於ARM Target的一種機制,可將來自應用程序代碼的輸入/輸出請求傳送至運行調試器的主機(Debug用的PC機)。例如,使用此機制可以啓用C庫中的函數,如:printf()和scanf(),來使用主機的屏幕和鍵盤,而不是在目標系統上配備屏幕和鍵盤。

半主機是通過一組定義好的軟件指令(如SVC, Supervisor Call)來實現的,這些指令通過程序控制生成異常。應用程序調用相應的半主機調用,然後調試代理處理該異常。調試代理提供與主機之間的必需通信。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章