如何區別二進位32個1表示-1,還是4294967295?
被這個問題折磨了好久。
十進位負數轉換為二進位,先求二進位碼,再求反碼,再加1補碼。這些概念都是簡單的數值計算,正常人都能明白。可是憑什麼二進位32個1(舉個例子)表示的是-1?那4294967295用二進位表示也是32個1,這不是衝突了嗎?難道有一個東西用來標識一個是二進位碼,一個是補碼?可是並沒有這個東西啊!(或許是我不懂)
小弟我智商愚昧,百度谷歌並用也沒找出答案。所以來「知乎搜索」請教各位大牛朋友,感激不盡。--------------------------------------------------------------------------------------------------------------------------------------------非常感謝大家! 我是一名前端工作人員,在學習位運算的時候遇到這個問題。每個回答都仔細看了,大家都說得很好。這一類不好描述的問題,搜索起來真是費勁。幸好有知乎和大家的幫助。祝大家工作順利。
恭喜你答對了:不能區分。
這是馮諾依曼體系結構的核心。不僅不同類型的數據之間不做區分,連數據和代碼都不做區分(當然實際的系統會有一些措施進行一定程度的區分,比如數據和代碼分段存儲、設置可執行許可權等來提高系統的安全性,防止惡意攻擊)。計算機發展初期倒是有那種對不同的數據類型加以區分的計算機,後來被淘汰了。
一段內存到底表示什麼,依賴於你如何解釋它,而不僅僅是看裡面每一位的狀態。比如在C語言里,有一段內存是連續32個1,你把它按照一個int來解讀就是-1,按照unsigned int來解讀就是4294967295,甚至你還可以把它當成一個指針、一條指令來解讀,全憑你的喜好。
如果你看了CSAPP第二章會更清楚。二進位意義上這個是不能區別的,同樣的32個1可以有無數種意義。所以才有了類型這種東西。
你很難理清楚這個世界上到底有多少種信息。中學的時候交給數學老師的作業本上可不用區分什麼-1和4294967295,只要是互相能夠理解的符號就足矣。為什麼計算機要這麼麻煩,一定要我們把所有的東西都用二進位來表示呢?因為計算機只會二進位。有了轉換,就有從這個轉換結果轉換回原內容的過程,這就是編碼和解碼。
也許32個1用來表示4294967295在數學上非常有道理,因為剛好是2的32次方減1,剛好是二進位和十進位之間的相互轉換。為什麼不用它來表示66666666這個數呢?如果我畫一個表,裡面把從32個0到32個1的所有二進位排列都對應一個十進位數,可不可以?當然沒有問題。反正從二進位排列這個集合到十進位數這個集合存在著一個一一映射關係,這個關係就是我們的編碼和解碼方式。之所以選擇二進位和十進位之間的相互轉換,是因為它最好理解,也最好實現。這裡的映射,是數學上的,計算機解碼的過程不可能真的去查表,只需要結果正確就可以了。這種映射,不僅僅是從二進位排列集合到十進位數集合的,也可以是從二進位排列集合到其他任何東西的。只要我們規定了編碼和解碼方式,它就可以準確無誤地用來表達我們想要的信息。
所以C語言里的類型的本質是什麼呢?就是不同的解碼方式。
- int類型,解碼方式就是所謂的二進位補碼
- unsigned類型,解碼方式就是普通的二進位數
- char類型,解碼方式是ASCII碼錶
- double和float類型,解碼方式是對應的浮點數標準,比如IEEE 754
所以一個單獨的二進位序列沒有任何意義,它需要一個類型,來完善它的含義。全1位的int和全1位的float,意義是完全不一樣的,和各自類型的量做運算,結果也是不一樣的。而這裡的加、減運算,也可以看作是一種映射。
- 000000 + 000001 = 000001
- 000000 + 000010 = 000010
- 000000 + 000011 = 000011
以此類推。這裡定義的加法關係很特殊,剛好是數學上的加法,計算機硬體能夠快速實現。如果我自己要定義一種奇怪的特殊的加法,那我可能又需要查表了。
但無論如何,C語言的類型總是圍繞著二進位的表示形式展開的。如果你忽略具體的二進位位,把目光聚焦於我們這裡定義的若干映射關係當中,那麼就可以忽略掉類型作為解碼方式(某種映射)這個事實,而是將類型作為集合來看待,所謂函數無非是類型集合和類型集合之間存在的映射的話,恭喜,你來到了一個新的世界。不過那裡,好像已經離我們的題目太遠了。
cpu本身並不知道這個數字到底有沒有符號,而你的程序知道,你的程序之所以知道,是因為你編程的時候規定了類型。
舉個例子,比如
printf("%u",-1);和cout&<&<(unsigned)(-1);還有if ((int)(4294967295) &< 0)......計算機並不區分。
不要說它有沒有符號了,實際上它到底是個32位整數還是個單精度浮點,也是不區分的。
那麼,為什麼它是整數?因為你聲明它為整數,編譯器就把這個變數上的操作都使用整數操作,於是運行時它就像一個整數那樣行為。
當然是靠題主欽定個類型啊
如何區別fly這個詞是指飛行還是蒼蠅?脫離具體場景並無意義,所以得看你這32個1是做有符號數補碼還是無符號數補碼解釋具體到題主這個問題,這樣理解比較好,先不考慮具體存儲限制:補碼,-1是……111111111111111111111111111111111(32個1前導無限個1)而4294967295是……011111111111111111111111111111111(32個1前導無限個0)但是,計算機中存數字,只能存有限位數,所以有符號32位整數用最前面的位表示前導是無限個1還是無限個0,表示範圍是-2^31~2^31-1而無符號數默認是前導無限個0,所以32位無符號整數表示範圍是0~2^32-1
這要看數據類型是 int 還是 unsigned int
不能解釋,嘗試 int a = -1; *(unsigned int *) a 取值變成了正數,因為系統以無符號整數類型按位cast了有符號整數,數值的多少取決於用什麼方式解釋,而這種解釋方式亦是程序規定的。
不同的計算機語言會做出不同的解釋,例如Java,它的int類型是有符號的(大家都知道java中沒有無符號整形),因此它的int(32位)取值範圍就是-2147483648~2147483647,它的int類型的二進位編碼方式就是正數直接二進位,負數使用補碼。因此在java中32個1就是-1,不會出現誤解。如果你要給一個int賦值為4294967295,會報錯Integer number too large。
看以下的例子:public class Test32_1 {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i &< 32; i++) {
sb.append("1");
}
System.out.println(sb);
int i = Integer.parseUnsignedInt(sb.toString(),2);
System.out.println(i);
System.out.println("MIN_VALUE = "+Integer.MIN_VALUE+", binary = "+Integer.toBinaryString(Integer.MIN_VALUE));
System.out.println("MAX_VALUE = "+Integer.MAX_VALUE+" , binary = "+Integer.toBinaryString(Integer.MAX_VALUE));
}
}
11111111111111111111111111111111
-1
MIN_VALUE = -2147483648, binary = 10000000000000000000000000000000
MAX_VALUE = 2147483647 , binary = 1111111111111111111111111111111
數據本身沒有任何意義,要看程序或者代碼如何定義或解釋它
計算機中的二進位數據本身沒有含義,是各種數據解碼器,即信息載體,賦予了它含義。
下面這個 C 程序,分別給出了在有符號長整形、無符號長整形、單精度浮點型的解釋下所表示的數據(運行環境 Win32 控制台,Visual Studio 2010):
// Test.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include &
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
long int i = 0xffffffff; // 二進位的 32 位 1 用十六進位表示就是 8 個 f
long int* pi = (long int*)i;
unsigned long int* pu = (unsigned long int*)i;
float* pf = (float*)i;
cout &<&< "INT: " &<&< (*pi) &<&< endl; cout &<&< "UINT: " &<&< (*pu) &<&< endl; cout &<&< "FLOAT:" &<&< (*pf) &<&< endl; return 0; } // _tmain()
最後輸出結果:INT: -1
UINT: 4294967295
FLOAT:-1.#QNAN
貌似浮點數還表示不出來,也就是說,如果一個浮點數的存儲信息是 32 位 1 的話,那這就是一個無效的浮點數。
其實,計算機中的二進位序列,可以表示任意信息。比如你在電腦上存儲的 jpg 格式的圖片、mp3 格式的音樂等,它們本質上仍然是由 0 和 1 組成的二進位編碼,但是它們並不僅僅是圖片或音樂,如果把它們的二進位編碼拆解,每 8 位組成一個數字,那麼它也同樣可以是一個數字表。或者用 ASCII+ANSI 的字元編碼來轉換,你也能看到一個文本文件。不信你用記事本打開它們試試?你能打開它,但是裡面的內容完全看不懂是不是?
我們還可以做個反向實驗,將一個純文本的 txt 文件用音樂播放器打開,結果你發現打不開。這是因為,音樂文件在其文件頭部加上了識別信息(同樣是一段有規律的二進位序列,且不是語言學裡有意義的字元序列),以用來向解碼器說明「我是一個音樂文件,你可以正確讀取我」。純文本文件在誕生之初就沒有頭部識別信息的概念,所以任何信息都可以用純文本的介質做載體,但反過來不行。
歸根到底,信息需要一個正確的載體,它才有了價值。同樣的信息,如果裝在了錯誤的載體上,那麼它就變得沒有價值了。就跟題主提的問題一樣,32 個 1 這個二進位序列,也需要一個合適的載體來解讀它。計算機本身不能區分,對計算機來說就是32個1……是人為規定的它等於-1或者2^31,對了,是人為規定的!!!!!!
只給一個數不能區分,要看變數類型
內存里是看不出的,你連這玩意是啥都不知道,整數,浮點數據,代碼段?但是編譯器在編譯時會根據你的數據類型生產不同的彙編代碼
lw $t0, 100($s0) #int a25 = A[25];
lwu $t1, 80($s1) #int b20 = B[20];
A is an array of signed int,while B is an array of unsigned int.
你要知道數據類型,要不然怎麼解析?說不定這是壓縮後的編碼,原值是談笑風生呢。
如果答主有時間而且感興趣的話,可以參考樓上大大所說的CSAPP(就是深入理解計算機系統),裡面第二章節會有詳細的講解啦.我剛好是看完了第二章,而且也對每章節做了點筆記,之前也遇到過題主的問題,附上個人的小博客:GanGan_新浪博客希望對你有用同時也歡迎指出不足之處~
我認為在內存里應該是看不出的。。。但是c的編譯器知道啊,如果用的是int,他在處理的時候會用帶符號的彙編指令,如果用的是uint,他在編譯時無符號彙編指令。
以上是我猜的。。。哈哈哈。。。
題主你問了個好問題呀!
(2016.09.03重新整理了一下答案。之前的回答自己回過頭來看,覺得說得不太清楚。)計算機處理整型數據用的都是補碼,僅僅看內存中四個位元組本身,確實沒法區分它是-1,還是2^32-1,而且我們也不用關心在內存中的數據的符號和類型,當數據進入CPU處理才關心它的符號和類型。幾個數據運算之後的真實值,不是CPU直接給出的,是軟體根據CPU運算之後通用寄存器的值以及各種狀態寄存器(包括但不限於進位標誌、溢出標誌、符號標誌、零標誌)的值,二次處理之後得到的。另外,一個4位元組的數,可能是一個地址(地址匯流排是32位的CPU,8086的地址沒這麼長)、有符號or無符號整型數或者float型數等等,就看軟體邏輯中用什麼指令操作它了。 推薦題主看一看「微機原理」這本書,它會解答題主的困惑的。實在不想看的話,題主也可以了解一下8086的彙編指令,以及簡單的四則運算的彙編代碼。我是小白一隻,剛剛會寫99乘法表,但我覺得這要看數據類型吧,如果是java的話,int類型是32位,最大值為2147483648(上網查的 ),所以如果要表示4294967295,在int類型里應該會出錯吧。不知道說的對不對。
Information Is Bits + Context
推薦閱讀:
TAG:程序員 | JavaScript | Java | C編程語言 | 二進位 |