上週幫一個讀工科的朋友核對實驗報告。他要算 sin(pi),螢幕上是 0.0548036651488,標準答案寫 0,他來回按了幾遍,說這東西算錯了。我把同一串字元在兩種模式下各跑一遍,結論當場翻過來:那個數不是雜訊,是他把 π 當成角度餵了進去;換成弧度,螢幕換成 1.224647e-16,這才是「理論上等於 0」那條式子的雙精度真身。模式、語法、浮點表示,混在了同一次按鍵裡。
先把三件事拆開
模式決定括號裡那個數按什麼單位讀:角度制下先乘 π/180 再進函式,弧度制下原樣進去。語法決定計算順序:冪右結合,階乘與百分號是後綴,一元負號排在冪之後,與單位無關。表示決定結果為什麼不會正好是 0:雙精度存不下 π,也存不下 0.1,於是 1.224647e-16 與 0.30000000000000004441 這類尾巴必然出現。
同一個表達式,兩列分開看
下表是同一串輸入在兩種模式下的結果,raw 是雙精度原始值,shown 是螢幕那行數:
| 表達式 | 模式 | raw | shown |
|---|---|---|---|
sin(pi) | DEG | 0.054803665148789530714 | 0.0548036651488 |
sin(pi) | RAD | 1.2246467991473532072e-16 | 1.224647e-16 |
cos(pi) | DEG | 0.99849714986386384208 | 0.998497149864 |
sin(30) | DEG | 0.49999999999999994449 | 0.5 |
sin(30) | RAD | -0.98803162409286182655 | -0.988031624093 |
0.1+0.2 | 四則 | 0.30000000000000004441 | 0.3 |
cos(pi) 在角度制下 shown 是 0.998497149864:它算的是 3.14159265359 這個角度的餘弦,跟 180 度的餘弦不是同一個問題。sin(30) 兩行差得更遠,一個讀成 30 度,一個讀成 30 弧度。
語法:三個容易被當成 bug 的結論
這塊跟模式無關,純粹是解析順序。2^3^2 的 shown 是 512:冪右結合,先算完 3^2 那一支再拿 2 去乘方,從左往右讀得到的 64 不是它的口徑。2^3! = 64,後綴的 ! 比冪更緊。-2^2 = -4,一元負號排在冪之後,跟數學裡把 −x² 讀成 −(x²) 是同一條規矩。2+3*4 = 14 對 (2+3)*4 = 20,sin30+1 在角度制下 = 1.5,無括號的函式只吃緊跟的那個數。
數字清單:上限與邊界
- 階乘只接受非負整數,最大到
170!:shown=7.257416e+306,raw=7.2574156153079940454e+306。再往上一個整數,頁面不會回傳數字,只給超出範圍的說明;雙精度能顯示的上限是 1.797693e+308。 - 精確整數上限是 9007199254740991,即 2^53-1;到 1e21 這個量級,1e21+1-1e21 得 0,那個加進去的 1 已經被吃掉。
- 百分號是後綴運算,讀作除以 100:
200*(1+10%)的 shown 是 220、raw 是 220.00000000000002842;(1+10%)^12-1= 2.13842837672,跟把 10% 乘 12 是兩條路。 - 千分位逗號與科學記號法都吃:
1,000/8= 125、1000*(1+4.7%)^7= 1379.19848604。逗號只用於分組,站錯位置直接報錯。 - 常數與函式:pi = 3.14159265359、
degrees(pi/3)= 60、log2(1024)= 10、log(8)/log(2)= 3。
一整列中間值怎麼核
那天要核對的不是一道題,是一整列中間值,逐個按再逐個抄,抄錯的風險比算錯還大。我用的是 科學計算器:冪、階乘、百分號、取餘、隱式乘法能混寫,一行一式貼進去一起算;原始值與顯示值分兩行給,模式標著 DEG 還是 RAD,Ans 引用上一次結果,最近 20 條算式留成歷史。角度制與弧度制可切換,除零、負數開根號、超範圍階乘都會說明原因,表達式只在瀏覽器本機算完。求值走自寫的詞法分析與調度場演算法,全程不呼叫 eval,所以我敢把別人一整列算式貼進去。
想把某個常數換成別的進位看一眼,用 進位轉換器;那道題本來就該拿分數做四則、不該先看小數尾巴的時候,交給 分數計算器。
Q:DEG 和 RAD 到底什麼時候用哪個?
看括號裡那個數的單位。題目寫清是度數的用角度制,sin(30) 給 0.5;表達式裡出現 pi,或者在驗恆等式的切弧度,sin(pi) 落到 1.224647e-16 這個量級才對得上。degrees、radians 不受開關影響,radians(180) 始終是 3.14159265359。
Q:為什麼不顯示完整小數?
顯示行按有效數字歸一過,自動口徑是 12 位,要看真實值就對照原始行。0.1+0.2 的 shown 是 0.3,raw 是 0.30000000000000004441;sqrt(2)*sqrt(8) 顯示 4,原始值是 4.0000000000000008882。那是表示誤差,不是這一次按錯了。
Q:171! 為什麼沒有結果?
它落在雙精度能表示的範圍之外,頁面給的是超範圍說明而不是一個數。要整數比值就在溢位前先相消:12!/(5!*7!) = 792、5!+3! = 126,比把階乘單獨攤開算安全。
Q:能不能給變數賦值、把中間量存下來?
沒有變數,也沒有自訂函式:字母只認已登記的函式名與常數,其餘會報錯並指出位置。中間量走 Ans 或 MS / MR / MC。不支援多參數函式,換底寫 log(8)/log(2) = 3;求和、連乘那類記號也不認。
回到我朋友那句「是不是算錯了」。我的順序是:先看模式那欄寫的是 DEG 還是 RAD,它決定 pi 讀成 3.14159265359 度還是弧度;再看語法,冪、階乘、一元負號最容易跟手算的讀法岔開;最後才看尾巴,raw 的位數決定你看到的 0.3 是不是嚴格成立。這樣 sin(pi) 顯示 0.0548036651488 是單位問題,顯示 1.224647e-16 才是表示問題:前者只有換模式才有意義,後者該設容差判斷,不該期待一個 0。