您好,登錄后才能下訂單哦!
這篇文章主要講解了“計算機的數值問題有哪些”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“計算機的數值問題有哪些”吧!
機器數:數值在計算機內部的編碼,也就是實際存儲的 0/1 序列。
真值:機器數想要表示的實際數值,可理解為現實生活中我們平常所用的有正負號的數。
機器數與真值的對應關系就是數值在計算機內部的編碼,主要有 4 種:原碼,反碼,補碼,移碼。
原碼由 1 位符號位和數值部分組成,數值部分就是真值的絕對值。
特點
與真值的對應關系直觀
0 的表示不唯一,有 +0(00...000) 和 -0(10...000)。
加減法復雜,需要判斷符號
反碼由 1 位符號位和數值部分組成。表示一個正數時同原碼;表示一個負數時,符號位不變,數值部分各位求反。
反碼相對于原碼并沒有多大改進提升,現已不用來表示數據。
補碼也是由 1 位符號位和數值部分組成。表示一個整數時同原碼;表示一個負數時,符號位不變,數值部分各位求反,末位加 1。
原碼到補碼快速轉換(負數):符號位不變,從右到左的第一個1不變,其他位取反。 補碼到原碼(負數)轉換方法同上,符號位不變,數值部分各位求反,末位加 1。也就是說符號位不變,從右到左的第一個1不變,其他位取反。
原因:按理說補碼到原碼應為原碼到補碼的逆操作,即減 1 再求反。設補碼為A,則原碼為 ~(A - 1),即11...111 - ( A - 1) = 11...111 - A + 1,也就是~A + 1。 因此 ~A + 1 = ~(A - 1)
一個數的補碼取負:連同符號位各位求反,末位加1,即從右到左的第一個1不變,其他位取反。
例如 4 位補碼,[x]~補~ = 1011,[-x]~補~ = 0101
特點
0有唯一表示00...000,所以補碼能表示的數范圍比原碼反碼多1,表示范圍為 -2^n-1^ ~ 2^n-1^-1
加減法符號位能直接參與運算。
現代的計算機整數基本都采用補碼表示。
移碼主要用于浮點數的階碼部分,后面會講浮點數的階有正負,兩個浮點數比較時需要比較階碼來対階。為方便比較,將階加上一個偏置常數使其變成正數,因為加的都是同一個偏置常數,階的差值也是不會改變的。
所以移碼就是數值本身加上一個偏置常數得到,偏置常數一般是2^n-1^,或者2^n-1^-1。
上述都是定點數的表示方法,定點數顧名思義,小數點是約定不動在一個固定位置的。定點數分為定點小數和定點整數。
定點整數的小數點固定在數的最右邊,一般用來表示整數。
定點小數的小數點固定在數的左邊,一般表示浮點數的尾數部分。
而浮點數的表示類似于科學計數法,它的指數部分可以變動,相應的尾數部分也跟著變化,就像小數點在浮動一樣,所以叫做浮點數,浮點數后面再詳解。
整數分為無符號整數和有符號整數,**給定一個數,在計算機里如何存儲,表示成 0/1 序列是編碼的事,而對這 0/1 序列如何解釋是上層軟件的事情。**如c語言中可解釋為有符號數和無符號數,而java中只解釋為有符號數。
數值比較時,得確定類型才能比較。通常默認為有符號數相比,若出現無符號數,則按照無符號數相比。
概念模糊抽象,看下面的經典例子來理解:
第一個:兩數按照有符號數相比,11...111B(-1) < 00...000B(0) 第二個:0U為無符號數,所以兩數按照無符號數相比,-1的機器數為11...111,按照無符號數解釋為2^32^ -1,這時是大于0的。11...111(2^32^-1) > 00...000(0)
注:括號前的二進制表示為數值的機器數,通常由補碼表示,括號里面的是c語言對機器數 按照無符號數或者有符號數 解釋出來的數。
看第二個例子前補充一些知識:
判斷一個常量類型時是先將符號“踢開”,只看后面的數值在哪個區間,根據區間確定類型。
c90 和 C99 是c語言的兩套標準,兩套標準對常量的處理不同,下面的例子以 C90 說明
第一個:2147483647為 int 型,兩邊按照有符號數相比,01...111B(2^31^ - 1) > 10...000B(-2^31^)
第二個:2147483647為 int 型,前者帶有U表示無符號數,按照無符號數相比,01...111B(231 - 1) < 10...000B(231)
第三個:2147483648為 unsigned int 型,但被強制類型轉換為int型,所以按照有符號數相比,01...111B(2^31 -1^) < 10...000B(-2^31^)
第四個:2147483648為 unsigned int 型,兩數按照無符號數相比,01...111B(2^31^ -1) < 10...000B(2^31^)
完全理解上述幾個例子后對數值比較這一塊應該沒問題了,在此再強調總結一番。
一個數據怎么存儲成 0/1 序列是編碼的事,怎么解釋這個 0/1 序列是上層軟件的事。
也就是說上述的數值比較中 2147483648 的機器數始終是10...000B,2147483647的機器數始終是01...111B,之所以出現不同的比較結果是因為 c 語言對它們進行了不同的解釋處理。
有了上面的基礎,再來看幾個程序:
打印結果如上圖注釋,原因如下:
x 的機器數為 11...111,u 的機器數為 10...0000 所以x按照無符號數解釋為2^32^ - 1 = 4294967295,按照有符號數解釋為 -1。 u按照無符號數解釋為 2^31^,按照有符號數解釋為 -2^31^
由上也可以看出機器數為10...000的數,是能表示的最小整數,取負后溢出還是它本身。
這是計算數組元素和的一個函數,按照程序所設想,length 傳入 0 時應該返回 0,但實際上并非如此。這個程序理論上會無限循環,實際運行時會發生數組越界導致異常。
理論上無限循環的原因:len 為無符號數,傳入0時,len - 1 為 -1,機器數為11...111,按無符號數解釋為2^32^ -1,是 32 位能表示的最大整數。
前面說過,有無符號數參與比較時,兩邊都按照無符號數相比,所以不管 i 怎么變化,始終小于等于右邊那個最大的值。當 i = 2^32^ -1 時,下一步又會回繞到0;
但實際運行時,肯定不會有那么長的數組,所以會發生越界錯誤。
此函數時用來比較兩個字符串str1, str2誰長,用到了庫函數strlen(),其原型如上。此函數設想 str1 長時返回 1,否則返回 0;但實際上只有 str1 和 str2 長度相等時會返回 0,其他時候都返回1;
問題就出在函數strlen()的返回值是size_t,即unsigned int。也就是說比較是按照無符號數來比較的,無符號數永遠是大于等于 0 的,所以只有兩個串兒長度相等時會使左邊式子等于 0,其他時候左邊結果的機器數中肯定有非 0 位,那么按無符號數解釋就會大于0,也就返回1了。
改進方法:返回語句改成return strlen(str1) > strlen(str2),直接讓兩個串的長度比較,而不是做減法再與0比較。
這個例子說明調用庫函數也要小心,對其的返回類型,參數類型要有了解,否則可能就會出錯。
IEEE 754浮點數標準規定的浮點數格式如上圖所示,簡要解釋如下:
符號位:1表負,0表正
階碼用移碼表示,是一個定點整數,偏置常數是2n - 1,所以單精度的偏置常數位127,雙精度的偏置常數位1023
尾數是一個定點小數,實際表示24位有效數字,隱含了一位 1 ,實際尾數為1.*****B
負數的表示只不過是符號位發生變化,所以32位單精度浮點數能表示的極值如下:
最小正數:2-126
最大整數:(2 - 2-23) * 2127
最大負數:-2-126
最小負數:-(2 - 2-23) * 2127
64位雙精度浮點數原理同上,能表示的極值如下
最小正數:2-1022
最大整數:(2 - 2-52) * 21023
最大負數:-2-1022
最小負數:-(2 - 2-52) * 21023
所以IEEE 754 浮點數能表示的范圍如下面數軸圖所示:
浮點數每個 2^n-1^~2^n^ 區間內能表示的數的個數都是2^23^個(尾數23位),而且等距。比如 1~2 之間有 2^23^ 個數,2~4 之間也有 2^23 ^個等距的數,所以浮點數表示的密度并不相等,距離0越近,密度越大。
也就是說并不是每個小數都能精確表示,當輸入一個不可表示的數時,機器會將其轉換為最近能表示的數。這也是為什么編寫程序時不要用浮點數來進行比較,特別是相等的情況,因為你想比較的數可能無法表示,機器自動給你轉換了。
+0 或者 -0,正負看符號位
+∞,-∞,正負看符號位
階碼全1尾數非0,NaN(Not a Number),不是一個數,0 / 0, 0 * ∞ 等會產生NaN。
從上述的數軸圖中可以看出-2^-126^ ~ 2^-126^之間的數是表示不了的,引進非規格化數可解決這問題。非規格化數的尾數中的隱含位為0,階碼雖然全0,但階還是-126。如此便在-2^-126^~2^-126^之間添加了2 * 2^23^個數,解決了下溢問題。
同樣,有了上述的基礎知識,來看一些例子:
這幾個題都很簡單,注意幾點就行:
精度大的轉換成精度小的可能會出問題,精度小的轉換成精度大的不會有問題。
浮點數取負直接變符號位就可。
最后一個是浮點數的加減運算問題,后面詳解,在這可以舉個例子簡單理解下:當 d 很大 f 很小時,d + f 得到的結果就等于 d, 舍去了f,所以左邊等于 0 ,右邊等于 f,兩邊不等。
這兩種運算比較簡單,只是要區分一下概念。
按位運算恰如其名,是對數值的位進行與或非運算。
邏輯運算的操作數只有 true 和 false,對數值的處理為非零即真。
移位分為邏輯移位和算數移位:
邏輯移位:不考慮符號位,左移時高位移出,低位補0;右移時低位移出,高位補0
算術移位:考慮符號位,左移時高位移出,低位補0;右移時低位移出,高位補符號位;
c語言中編譯器進行移位運算和CPU進行移位運算是不一樣的:
編譯器:進行實際移位,比如移動w位,實際也移動w位
CPU:移動 w % k ,w為所移位數,k為數據類型的位數
看下面程序幫助理解,打印結果已注釋在后面
擴展分為 0 擴展和符號擴展,數據轉換時,短數向長數轉換時需要位擴展。
0擴展用于無符號數,在原來的數前面添足夠的0即可。
符號擴展用于有符號數,在原來的數前面添足夠的符號位即可。
位截斷,長數向短數轉化時會發生截斷,規則比較粗暴簡單,直接“砍掉”高位,留下低位即可。
長數的表示范圍肯定大于短數,所以截斷一個數可能會改變原來的值。看下面一個例子:
經過截斷再擴展后 32768 變成了 -32768,所以再截斷時要注意溢出問題。
計算機里整數浮點數的加減乘除運算的實際過程都很復雜,內容很多,建議直接看唐朔風的計算機組成原理第六章,數字邏輯相關書籍中加法器,乘法器等的電路實現。深入理解計算機系統對各種數值算法的理論推導。公眾號內回復電子書即可獲得相應書籍。下面就只說說其中我認為比較重要需要的一些東西。
現代計算機里面整數都可以看做是用補碼表示的,統一了加減法,符號位也能和數值部分一起進行計算。
不論是無符號數還是有符號數,都先按照實際的機器數做運算,得到的結果再解釋成相應的無符號數或有符號數。
整個計算機的運算系統都是采用模運算,得出的結果如果超出計算機表示的位數,會直接丟掉高位。
例如若兩個數為a, b,對應的機器數為A, B,則a + b 相應操作為 A + B,a - b 為A + ~ B + 1。再對計算得到機器數解釋成最終結果。
乘法主要需要考慮溢出問題,n 位數乘 n 位數,結果可能需要2n位來表示。c語言中截直接斷成 n 位來實現(來自深入理解計算機系統),但經過試驗結果似乎會自動擴展。
只要結果在你機器的表示范圍內就會自動擴展,比如兩個 short 型的數相乘結果會自動擴展成 32 位來正確表示結果,除非限定結果還是個 short 型,才會截斷。
整數除法一般沒有溢出問題,因為商的絕對值不會大于被除數的絕對值。
除了一種情況,有符號數中最小數除以-1,例如 int 型的情況:-2147483648 / -1 便會溢出,結果還是 -2147483648 本身。
在這說點 c 里面一些有趣的東西,可以算是bug吧。上述說的有符號數中的最小數,即機器數為10...000的數(設為s)取負后還是它本身,這是沒問題的,機器數也的確是相同的。所以按理說 s = -s。這在32位int型,64位long long的情況下成立,但是在short情況下不成立。所以 c 里面關于數值的東西有許多奇奇怪怪的東西,諸位感興趣可以去嘗試。
乘除法運算所花的時間遠遠多于移位加減運算的時間,因此,編譯器處理變量與常量乘除時會以移位,加法,減法的組合運算來代替乘除法。
來看一個具體例題:x為一整形變量,現要計算55 * x,給出一種計算表達式使得所用的時鐘周期最少。
題目很簡單,主要是想說明怎么轉換。
乘以 2^n^ 相當于左移 n 位,除以 2^n^ 相當于右移 n 位。 **左移需要注意高位的溢出問題,而右移則需要注意舍入問題。一般的舍入規則是向0舍入,但用移位來實現除法是向下舍入的。**對于正數來說沒什么問題,向下舍入就是向0舍入。但是負數就有問題了,向下舍入并不是向0舍入,需要校正。
為什么移位來實現除法是向下舍入的呢,正數應該很好理解,右移之后丟掉移出的小數部分,數值自然變小了。如果是負數,右移之后丟掉小數部分數值不應該變大嗎?但別忘了負數在計算機里由補碼表示,它跟真值對應的關系是要求反的,所以換成真值后會發現數值依然是變小的。
來看一個例子理解向下舍入,應該會更清楚
如何校正呢? 既然負數也是向下舍入,那么在它移位之前先給它加上一個偏移量讓它變大點,那么移位后舍入不就正確了。
如果右移n位,這個偏移量就是2^n^-1,原來是 x / 2^n^,現在是(x + 2^n^ - 1) / 2n = x / 2^n^ + (2^n^-1 / 2^n^);相當于加了一個"極限小數"來校正。
這個數可以在十進制下來理解,比如移動一位也就是一位小數的情況下,-2.1,-2.9都要舍入到-2,應該怎么操作呢?將兩個數都加上一個0.9就行了,這里0.9就是十進制一位情況下的一個極限小數,換成二進制同理,二進制 n 位的一個"極限小數"就是2^n^-1 。
1、対階
只有階數相等,尾數才能直接相加減。 対階原則:小階對齊大階,階小的尾數右移,右移尾數為階差。
注: 右移時注意隱含位 1 也要一起參與移位,為保證精度,低位移出的位不要丟掉,后續參與尾數加減。
2、尾數加減
尾數是由定點原碼小數表示,這里沒有符號位,所以加減就是普通的二進制加減法。這里注意隱含位和対階時移出的附加位也要參與運算。
3、規格化
上述尾數加減后得到結果可能五花八門,千奇百怪,需要規格化變成 IEEE 754 的標準形式。**簡單說來就是把尾數變成1. **B的形式,然后相應的調整階碼。小數點的位置與階碼息息相關,小數點浮動了,階碼當然也要相應變化。
4、結果舍入
対階和尾數規格化的時都可能右移,為保證精度,會將移出的位保留下來參與中間運算,得出最終結果后再舍入。IEEE 754規定至少保留 2 位,緊跟在尾數右邊的叫做保護位(guard),保護位右邊的叫做舍入位(round),為提高精度,舍入位右邊還有一位粘位(sticky)。只要粘位右邊有任何的非0數就置1,否則置0。
5、階碼溢出判斷
結果的階碼全 1 表上溢,產生異常或者結果置為∞。 結果的階碼全 0 表下溢,產生異常或者結果置0
這就能解釋前面為什么 (d + f ) - d 不一定等于 f ,d 如果很大,f 很小,対階時f 看齊d,尾數可能一直右移導致有效位沒有了變成了全0,再進行尾數加減時 d 值不變。所以左邊等于0,右邊等于 f,兩者不等。
浮點數乘法運算過程類似加減法,主要區別在于乘除法不用対階,其他過程基本一樣。也好理解,就像我們平時用科學計數法做算數時,加減法那肯定是需要指數相等,尾數才能相加減;而乘除法時可以直接尾數與尾數運算,指數與指數運算,一個道理。
感謝各位的閱讀,以上就是“計算機的數值問題有哪些”的內容了,經過本文的學習后,相信大家對計算機的數值問題有哪些這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。