[英]Hex float representation in c
當我在C中讀取浮點數的十六進制表示法時,我會遇到一本來自Stephen Prata書的特殊數字“0xa.1fp10”。 當我將此數字分配給浮點數或雙變量並使用printf
“%a”格式說明符打印時,結果為0x1.43e000p + 13,與原始數據不匹配。 但兩者都是十進制的相同值10364。 到底是怎么回事? 為什么產值會發生變化? 如何將原始數字作為輸出?
不幸的是,你無法從printf
獲得相同的格式0xa.1fp10
。 C標准規定%a
的輸出是這樣的,對於非零的正常雙精度,在前面有一個非零數字.
並且需要多少個數字來表示完全在...之后的值.
。 實現可以選擇第一位中有多少進入第一位!
然而,C11標准的腳注278表明了這一點
二進制實現可以選擇小數點字符左側的十六進制數字,以便后續數字與半字節(4位)邊界對齊。
這就是問題所在。 由於IEEE 754 double
s具有53位尾數; 正常數字的第一位為1; 52位的其余部分可以被4整除,這個腳注之后的實現(我的機器上的Glibc似乎是一個),將始終輸出任何有限的非零浮點數,以便它以0x1.
開始0x1.
!
試試這個最小程序:
#include <stdio.h>
int main(void) {
for (double i = 1; i < 1024 * 1024; i *= 2) {
printf("%a %a %a\n", 1.0 * i, 0.7 * i, 0.67 * i);
}
}
我的計算機上的輸出是
0x1p+0 0x1.6666666666666p-1 0x1.570a3d70a3d71p-1
0x1p+1 0x1.6666666666666p+0 0x1.570a3d70a3d71p+0
0x1p+2 0x1.6666666666666p+1 0x1.570a3d70a3d71p+1
0x1p+3 0x1.6666666666666p+2 0x1.570a3d70a3d71p+2
0x1p+4 0x1.6666666666666p+3 0x1.570a3d70a3d71p+3
0x1p+5 0x1.6666666666666p+4 0x1.570a3d70a3d71p+4
0x1p+6 0x1.6666666666666p+5 0x1.570a3d70a3d71p+5
0x1p+7 0x1.6666666666666p+6 0x1.570a3d70a3d71p+6
0x1p+8 0x1.6666666666666p+7 0x1.570a3d70a3d71p+7
0x1p+9 0x1.6666666666666p+8 0x1.570a3d70a3d71p+8
0x1p+10 0x1.6666666666666p+9 0x1.570a3d70a3d71p+9
0x1p+11 0x1.6666666666666p+10 0x1.570a3d70a3d71p+10
0x1p+12 0x1.6666666666666p+11 0x1.570a3d70a3d71p+11
0x1p+13 0x1.6666666666666p+12 0x1.570a3d70a3d71p+12
0x1p+14 0x1.6666666666666p+13 0x1.570a3d70a3d71p+13
0x1p+15 0x1.6666666666666p+14 0x1.570a3d70a3d71p+14
0x1p+16 0x1.6666666666666p+15 0x1.570a3d70a3d71p+15
0x1p+17 0x1.6666666666666p+16 0x1.570a3d70a3d71p+16
0x1p+18 0x1.6666666666666p+17 0x1.570a3d70a3d71p+17
0x1p+19 0x1.6666666666666p+18 0x1.570a3d70a3d71p+18
此輸出是有效的 - 對於每個正常數字,代碼只需要輸出0x1.
然后將尾數的所有實際半字節轉換為十六進制,條帶尾隨0
字符並附加p+
后跟exponent。
對於長雙精度,x86格式具有64位尾數。 由於64位可以完全整除為半字節,因此合理的實現將在前面有一個完整的半字節.
對於正常數字,值從0x8
到0xF
變化(第一位始終為1),並且在該點之后最多15個半字節。
嘗試使用
#include <stdio.h>
int main(void) {
for (long double i = 1; i < 32; i ++) {
printf("%La\n", i);
}
}
看它是否符合這個期望......
在正正數和零之間可能存在次正規數 - 我的Glibc用0x0.
表示這些雙精度值0x0.
然后是尾數的實際半字節,刪除尾隨零,固定指數-1022
- 再次,表示是最容易實現和最快計算的。
這是一種十六進制浮點格式。 0x
之后和p
之前的數字(和周期)是十六進制數字。 那部分被稱為有效數字。 p
后面的數字是十進制數字,表示乘以有效數的2的冪。
在0xa.1fp10
,有效數是a.1f
。 這表示數字10•16 0 + 1•16 -1 + 15•16 -2 ,等於10 + 31/256或2591/256。
然后p10
表示將其乘以2 1024 ,因此結果為2591/256•1024 = 10,364。
結果只是一個數字。 0xa.1fp10
, 10364
,和0x1.43ep13
是代表相同數量的三種不同的標號。 將此值存儲在float
或double
,該對象僅包含該數字。 沒有原始格式的記錄。 使用%a
打印時,實現選擇前導數字1 。 因為沒有原始數字的記錄,所以除非您有一些單獨的信息記錄並編寫自己的軟件來打印數字,否則無法使printf
生成原始字符串。
浮點格式通常使用二進制基,並且很難編寫能夠將十進制科學記數法正確轉換為二進制浮點的優秀軟件。 (這是已發表論文的一個已解決的問題,但並不總是使用好的軟件。)使用十六進制格式而不是十進制格式可以很容易地在浮點數中准確指定作者想要的值,並且編譯器很容易解釋它。 十六進制格式是為此目的而設計的:讀取和寫入浮點數的簡便性和准確性。 它不是為了促進審美問題而設計的,例如再現特定縮放或標准化。
1當使用%a
,C標准將其留給實現選擇所使用的縮放,只是在小數點字符前面只有一個數字,如果數字在正常范圍內則為非零。浮點格式,以及該點后的位數等於精度。
但兩者都是十進制的相同值10364。
確實。
到底是怎么回事? 為什么產值會發生變化?
為什么不改變? 內存中double
表示不攜帶任何格式信息。 正如您自己觀察到的那樣,輸出表示與輸入相同的數字,因此值沒有變化。 它的表現方式不同。
使用%e
指令也可以使用十進制數進行大致類似的行為。
如何將原始數字作為輸出?
很有可能你無法讓你的特定printf()
實現發出程序從其輸入中讀取的特定表示。 但是,如果有關於該表示的系統性,例如具有在小數點之前提供單個十六進制數字的最小指數,那么原則上您可以編寫自己的輸出函數來生成該表示。
在你添加的評論中,
但標准表示是什么?
在C語言標准所要求的表示意義上沒有一個。 該語言僅要求表示在小數點之前只有一個十六進制數字,並且如果數字被標准化並且本身非零,則它非零。 這為大多數標准化浮點數留下了四種可能性。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.