最近更新: 2005-09-10

語言的使用,會訓練並影嚮我們的思考方式。在電腦程式語言中,特別突顯這個現象

有人拿著一道程式語言的題目來問我如何解?我想了一下解法,再看了一下他原來的解法,發現語言的使用,會訓練並影嚮我們的思考方式。在電腦程式語言中,特別突顯這個現象。例如,一個熟悉組合語言的人 (例如我),在處理下面這個問題時,就算同樣用 C 語言,他的答案就和一個不曾使用過組合語言的人 (我朋友) 完全不同。

問題: 將10進位數字表示文字轉換成16進位數字表示文字。例: 輸入 255 ,輸出 0xFF 。讓我們看看一個用過組合語言的人,是如何寫出答案 (就是我寫的)。

/* C Language */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char**argv) {
    int n;
    int bytes, len, f, r;
    char HEX[256], *pHEX;
    const char *HEX_TABLE = "0123456789ABCDEF";

    /*
    本例只換算32bit整數。

    透過適當的字串轉數值的動作,下面的方式經過簡單地
    套上另一個迴圈後,可以處理無限大的數字。
    (字串轉換後的數值,放在 int 陣列中,並回傳總 bytes 。)
    */

    if (argc < 2) {
        printf("Usage: %s octNumber\n", argv[0]);
        exit(1);
    }
    sscanf(argv[1], "%d", &n);
    printf("Input: %d\n", n);

    bytes = size_t(n);

    for (pHEX = HEX, len = 0; n && len < bytes; ++len, ++pHEX, n = n >> 4 ) {
  	   *pHEX = HEX_TABLE[n & 0xF];
    }
    *pHEX = '\0';
    /* 現在儲存的字串,是倒過來表示的,接下來將頭尾對調。 */

    len = strlen(HEX);

    for( f = 0, r = len -1; f < (len/2); f++, r--) {
    	HEX[f] ^= HEX[r]; /*swap HEX[f], HEX[r] */
    	HEX[r] ^= HEX[f];
    	HEX[f] ^= HEX[r];
    }

    printf("Output: 0x%s\n", HEX);
    /*END*/
}

我是直接用遮罩的方式,再查表取字,以位元運算為主,這是組合語言式的正統做法。而我朋友是用數學換算,那似乎是數學式的正統做法。從計算機原理來看,我的方法最佳。在此無意說明計算機原理,僅討論語言與思維方式。兩種方法毫無疑問地都是邏輯結構 (不論數學或是電腦程式語言,這是語言本身的限定) ,但符號型式卻相差很大,這是因為語言使用上的特性,訓練並影嚮了我們的思維。

組合語言的使用,訓練了我對16進位數值與位元運算的思考習慣。一般數學,並不是這樣教人演算的。以前還有個數學研究所的人,問過我「超長整數」 (超過 unsigned long long 範圍的數) 相加的 C 語言程式怎麼寫。我一看他的構想就知道他還沒擺脫數學思考,我告訴他這不能直接用數值 (內建數值型態) 去做,要用字串儲存數,一字元對一字元地相加與進位。其實用過組合語言的話,非常快就想通了 (用組合語言寫更快)。

我數學不好,所以有個怪僻,只要有人拿什麼函數模型來請我幫他寫出電腦程式,我就會要他先把模型中的所有運算過程 (特別是微積分的) ,全部轉成只有加減乘除及邏輯運算的型式 (連指數、對數都不能用) ,我才能幫他寫程式。幾乎每個人都被這要求打退堂鼓。我數學不好,不了解原因。有任何數學好的人,可以告訴我這要求很難做到嗎?

相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/467296.html

樂多舊回應
未留名 (#comment-695961)
Thu, 20 Oct 2005 21:25:34 +0800
因為指數(以上...)都是用來描述狀態 而不是單純的數量計算

另外一旦做出來就能終生白吃白喝,被人拱到與愛因斯坦相同的地位XD
未留名 (#comment-706879)
Sat, 22 Oct 2005 22:41:41 +0800
邏輯系統是二進位系統,偽與真,0與1。

以我對數學的理解,數學家普遍認為偽與真是邏輯原子,所有數學式與數學分子都是由邏輯原子結構而成,也必然可解析回邏輯原子的型式,亦即解析回只用偽與真來表達的型式。

我所要求的,不過是要對方把微積分或含指數、對數等高級數學分子的數學式,解析回基礎的數學分子與邏輯原子的型式。在邏輯系統中,我亦十分堅信所有數學式必能以邏輯原子的型式表達。如果有任何人說他寫的數學式不能解析回去,那我便認為那個數學式是偽的、如小孩子塗鴉一般的圖案,那不能稱為一個數學式。

另一方面,將現象或狀態定質,定質而後定量,定量而後能計算,卻是不少實證主義的社會科學家所堅信的正確科學研究方法。這正對應到將指數、對數等高級數學分子解析回基礎數學分子的過程。

我並不信社會科學中實證主義那一套,其中一個理由在於,他們連我上述的要求都做不到。
zard1989@gmail.com(St. Kevin) (#comment-988490)
Mon, 12 Dec 2005 22:38:14 +0800
...基本上對數、三角函數都是可以用普通的運算表達出來的,不然一般的計算機跟math.h裡的函式是怎麼算出來的?
未留名 (#comment-2364431)
Thu, 22 Jun 2006 10:10:01 +0800
如果那個人能把複雜的數學式解析成加減乘除~我想程式也已經寫出來了~寫程式難的就是邏輯分析~寫大家都會寫~
未留名 (#comment-2391098)
Mon, 26 Jun 2006 21:05:20 +0800
GDE 重覆了我的問題,但沒有回答我的問題。建構數學式依賴的也是邏輯分析,為何那些人無法解析回去?
digitalize_chen@yahoo.com.tw(JSC) (#comment-4403581)
Wed, 11 Apr 2007 12:22:36 +0800
我不是數學家,
不過我想邏輯系統是二進位系統,而傳統數學為十進位系統。
數學發展至今一直都是以十進位為架構而衍生出來的,包括所謂的微積分、指數、對數等等。
如果真要將現行的所有運算轉為邏輯運算似乎也不是不可能,只是人類可能要以二進位系統重新建立一套新的理論架構,這似乎是一件浩大的工程,而且也沒有必要作這樣的事情。現有的技術都可以直接做轉換了,即使發展這樣的系統應該也沒有它的市場價值!
而那些人無法解析回去,我想大概是因為他們本身具備的思維是以十進位系統為基礎,若要求他們嘗試以二進位系統去思考應該是一件蠻困難的事吧!
未留名 (#comment-14996755)
Wed, 14 Nov 2007 18:46:19 +0800
數學並不是只有算術(一般的微積分多是算術的延伸),數學還有集合論、群論、拓樸學、序理論、公理化系統、歐氏幾何、數學邏輯、範疇論、圖論、證明論……
數論只是其中一個分支而已,就算是邏輯學也不一定是古典的一階邏輯,還可以是真觀邏輯(Intuitionistic logic)、模糊邏輯等等,而集合論邏輯亦已被證明大過一階邏輯,並不是全部集合論語句都可以轉換成一階邏輯語句。

十進制與二進制就是數論的研究範圍。

並不是所有十進制有限小數位小數都可以精確地轉換成機器能表示的二進制小數,因為有很大部份的十進制有限小數位小數都是對應到二進制無限小數位循環小數,何況是無理數等無限小數位小數?多數電腦計算和記錄的是一個近似值,明白這一點,即是對數學中的「嚴謹」有一點認識。

現代的電腦做不到的是發明和創造,即是包括建構式證明。因此電腦要計算微積分就要使用蒙地卡羅積分法、辛普遜積分法等等近似法作機械式重複計算。

值得注意的是,數學中的很多算術計算都是不分十進制、二進制或甚麼進制的。
未留名 (#comment-14996825)
Wed, 14 Nov 2007 19:00:06 +0800
我們要把各種數學模型轉換成基礎算術的組合才可以讓電腦計算是因為現代電腦無法做到建構式運算和處理無限所致,而不是二進制十進制的分別,更不是因為電腦用位元邏輯來「思考」。
未留名 (#comment-14997007)
Wed, 14 Nov 2007 19:38:56 +0800
『以前還有個數學研究所的人,問過我「超長整數」 (超過 unsigned long long 範圍的數) 相加的 C 語言程式怎麼寫。』
這只是他沒有想過用超過一個儲存單元來儲存數值、用字元串儲存數值和自行處理進位借位等問題吧了。
當然啦,懂一點組合語言的人都會懂得處理。
就算要精確處理有限小數位十進制小數亦可以如法炮製。
未留名 (#comment-18850663)
Wed, 01 Apr 2009 16:56:35 +0800
十進制轉換成十六進制根本不用那麼麻煩,
基本上當你把數字"放到"記憶體時,
就已經自動幫你轉成二進制了,
而4位元的二進制剛好就是十六進制,
所以其實直接到記憶體上"拿資料"就好了,
根本就沒什麼在計算的。

至於超長整數的相加,
還是離不開數學啊!
想想平常怎麼計算兩數相加的,
例如:97+18=115
先計算個位數部分的7+8,
結果是15,
先把個位數的5記下來,
再來就把進位的1和這兩數十位數部分相加9+1+1,
結果是11,
再把後面的1記下來放在個位數的那個5前面,
而百位數部分因為沒有數字相加,
所以就直接把剛剛的進位記錄下去,
所以結果就是115,
而上面的個位數、十位數和百位數,
其實可以想像成電腦的記憶空間,
而用越多的記憶空間來存放的話,
就能記算出越多位數的"長整數"了,
而這樣還是離不開數學啊!
只不過是電腦幫你把這些給計算出來了而已。
未留名 (#comment-18897269)
Fri, 10 Apr 2009 06:39:46 +0800
yannan在發言前應該先看把本文看完才是,才不會浪費時間打那麼多字。

本文例子說的是「10進位數字表示文字轉換成16進位數字表示文字」。也就是說把字串 "255" 顯示成字串 "FF"。

另外,關於超長整數的部份,你應該沒學過組合語言,不了解計算機的「進位機制」是有限制的,它的限制就是暫存器的位元數。
如果像你說的那麼簡單,我們現在也不會分 32bit作業系統、64bit作業系統。
未留名 (#comment-18956025)
Tue, 21 Apr 2009 03:41:25 +0800
我想LungZeno講到重點了
電腦無法處理"無限"的問題,只能用逼近的
所以電腦很適合處理離散的問題,對於連續的問題只能算到近似
未留名 (#comment-22064447)
Wed, 26 Oct 2011 10:20:18 +0800
yannan說的沒錯
基本上 sscanf(argv[1], "%d", &n); 這一行
就是把數字"放到"記憶體之時 其實已經做完了轉換
跟是不是 「10進位數字表示文字轉換成16進位數字表示文字」 已經沒有關係

其實只要兩行即做完
sscanf(argv[1], "%d", &n);
printf("Output: 0x%02X\n", n);
未留名 (#comment-22067015)
Thu, 27 Oct 2011 15:37:18 +0800
to Haku and yannan:

你們都沒注意到一件事,問題並沒有限定可輸入的數字字數。換句話說,若是輸入:
1234567890123456789012345678901234567890123456789012345678901234567890
這麼長的10進位文字,程式也能轉成16進位形式呈現。

在我的程式碼中,註明程式碼只針對32位元整數計算,但實際上還需要「套上另一個迴圈後,可以處理無限大的數字。」 請仔細看完全文,我寫的換算過程(第26到40行)一直都是針對不限定長度的數的輸出處理。

你們用 ANSI C 的 sscanf 處理,就會受到 sscanf 的實作限制,以對 %d 的選項來說,只能讓使用者輸入 int 型態所能接受的最大值。在32位元系統中就是 2 的 31 次方。

LungZeno 的回應就理解這個限制,知道這邊牽涉了需要程序員自行處理字元串儲存數值的事。
Explorer09@gmail.com(Explorer) (#comment-22404188)
Tue, 03 Apr 2012 09:55:51 +0800
我可以說 yannan 與 Haku 所提出的質疑不是沒道理的。

問題出在作者(遊手好閒的石頭成)所寫的程式碼其實分兩個部份
1. 把十進位轉成二進位 - 這是用 sscanf("%d") 達到的
2. 把二進位轉成十六進位 - 這是作者第26到40行所寫出來的程式

所以,
1. 程式的32bit的限制是 sscanf 造成的
2. 作者說能處理無限大的數字其實只有「二進位轉十六進位」這部份
3. 超過 4294967296 [2^32] 的十進位數字,作者並未講說如何處理,如果作者說的「另一個迴圈」是要慢慢除掉 4294967296 的話,那這程式比起其他人寫的也不會好到哪裡去。
Explorer09@gmail.com(Explorer) (#comment-22404234)
Tue, 03 Apr 2012 10:30:33 +0800
順便一提,作者用的「三次XOR代替一個暫存char空間」的這種swap方法很值得我學習。

下次我的SWAP macro 可以這樣寫:
/* Work with primitive types only. */
#define SWAP(x,y) ((x) ^= (y); (y) ^= (x); (x) ^= (y))
未留名 (#comment-22405044)
Tue, 03 Apr 2012 18:00:36 +0800
請直接跳過 sscanf 的部份吧。

我的題目重點是「將10進位數字表示文字轉換成16進位數字表示文字」,也就是 Explorer 列出的第二部份。

至於第一部份,我是懶得寫出可輸入無限大字串的字串轉數值程式,所以程式開頭講明我只處理32bit整數所能表達的字串數字,然後用直接偷懶用 sscanf 去讀入數值。

這只是篇blog文章,也不需要湊字數騙稿費,我不會沒事放上整套程式碼。

第一部份,要完整寫出的話,程式碼行數會很長,做法也不會是「用除的」。
因為真正的超大數沒有辦法存為原始數值(primitive value),也就不可能讓我們用 x/16 的方法去換算。

第一部份的實作方式,可以直接去看像 python ,ruby 這些程式語言解譯器的源碼。
喔,python, ruby 等程式語言中,最簡單 123 也是一個 object,而不是 primitive value。
所以對於超大數,它們內部不是用1個primitive value儲存數值,而是1組。

那些高階語言內部封裝了超大數處理時的複雜度,讓超大數的行為表現如同普通的四則運算。
這就與C或組合語言看超大數的態度有所不同了。C或組合語言是用位元與進位去處理的。