Excel 中的工程系列函式讀起來像是函式參考中最簡單的角落。DEC2BIN 將數字轉換為二進位字串。HEX2DEC 將其轉換回來。IMSUM 將兩個複數相加。每一個看起來都像是一個格式化練習。但事實並非如此。在這些名稱的背後,隱藏著自電腦架構課程以來大多數開發人員都未曾接觸過的十位元二補數編碼、完全存在於字串內部的複數格式,以及如果您在檢查前進行移位則會悄悄溢位 64 位元整數的位元運算子。完全複製 Excel 的試算表引擎無法對其中任何內容進行防護或四捨五入。
這些函式分為三組,每組都隱藏著不同的陷阱。進位制轉換涉及負數和每個基數的臨界值。複數運算涉及解析和格式化字串。位元運算涉及保持在 Int64 的範圍內。本文將介紹 HotXLS 所實現的每組功能,並提供您實際上會編寫的試算表呼叫。
進位制轉換與十位元二補數
正向轉換是每個人都期望的部分。DEC2BIN(9) 會給出 "1001",且可選的第二個引數會將結果靠左填補到固定寬度。陷阱是負數輸入。Excel 不會寫入負號。It encodes the value as a ten-digit two's complement string in the target base, which is why DEC2BIN(-5,10) returns "1111111011" rather than anything with a sign. 一旦值為負數,位數引數就會被忽略,因為編碼已經被固定為十位數。
十位數是固定的預算,而該預算設定了每個進位制的可表示範圍。在二進位中,翻轉為負數部分的大小是 512,且環繞模數是 1024,因此二進位字串僅在長度正好為十個字元且其值至少為 512 時才是帶正負號的。相同的概念適用於其他進位制。八進位使用 2^29 的半臨界值和 2^30 的完整模數。十六進位使用 2^39 和 2^40。HotXLS 讀取器精確套用此規則:它累積數位,且僅當字串寬度為十個字元且累積值等於或高於半臨界值時,它才會減去完整模數以恢復帶正負號的值。無論多大,九個字元的字串始終是非負數。
編碼器是鏡像。非負值會逐位元轉換,並可選擇以零填補到要求的寬度,如果它溢位基數的正上限,或者要求的寬度太窄而無法容納它,則會被拒絕。負值首先透過加上完整模數被帶入範圍內,這會將其轉換為其基數表示法始終為十位數的值,然後發送帶有前導零的數位以填滿寬度。單個共享範圍檢查(每個基數的對稱下限和上限)使 DEC2BIN、DEC2OCT and DEC2HEX 一致與另一個在邊緣保持彼此一致。
這留下了跨進位制轉換,例如 HEX2BIN 和 OCT2HEX,它們在函式名稱中不經過十進位的情況下變更基數。實作並未針對每個循序對攜帶單獨的常式。它使用來源進位制將輸入字串剖析為帶正負號的十進位值,然後將該十進位值格式化為目標進位制。十進位是樞紐。一個剖析常式和一個格式化常式組合在一起,涵蓋了每一種組合,且因為兩半都共用同一個十位數帶正負號的約定,所以負值在傳輸過程中能保留其正負號完好無損。
複數是字串,因此工作是剖析
Excel 沒有複數資料類型。複數值是字串 "a+bi",且 IM 系列中的每個函式都會接收這些字串並傳回一個。COMPLEX 從實部和虛部建立字串。IMSUM、IMSUB、IMPRODUCT 和 IMDIV 剖析其引數、對數值部分進行算術運算,並將結果格式化回字串。數值工作是大學代數。難點完全在於如何可靠地將文字轉換為兩個浮點數,這正是內部剖析器發揮作用的地方。
該剖析器中的兩個細節很容易出錯。第一個是裸虛數單位。字串 "i" 表示一倍的 i,不是零也不是錯誤,因此當字尾前方的係數為空或是孤立的加號時,剖析器必須將其讀取為值 1,而孤立的減號讀取為 -1。跳過這一點,IMSUM("i","i") 就不再是 2i。第二個是科學記號與分隔實部和虛部的符號相衝突。剖析器透過掃描加號或減號來尋找該分隔符號,但寫入為 "1.5E-3" 的數字包含屬於指數的減號。因此,當緊接在其前方的字元是 e 或 E 時,掃描會拒絕將加號或減號視為分隔符號。如果沒有該防護,實部將在指數符號處被撕成兩半,且剖析將在完全合法的輸入上失敗。
字尾本身被保留而不是被標準化。Excel 接受 i 和 j,且 HotXLS 會記住輸入使用的是哪一個,以便格式化後的結果帶有相同的字母。格式化隨後套用傳統的速記法:虛部為 1 時僅列印字尾,為負 1 時列印為 -i,虛部為 0 時折疊為普通實數,而實部為 0 時丟棄前導 0+。
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Engineering');
// Negative input: a ten-bit two's complement, places argument ignored.
Sheet.Cells[1, 1].Value := Sheet.Calculate('=DEC2BIN(-5,10)'); // 1111111011
// Complex multiply on two "a+bi" strings.
Sheet.Cells[2, 1].Value := Sheet.Calculate('=IMPRODUCT("3+4i","1+2i")'); // -5+10i
finally
Book.Free;
end;
end;
超越複數函式(其中包括 IMSQRT、IMEXP、IMLN 和 IMPOWER)在直角座標系中不起作用。它們將剖析後的值轉換為極座標形式,對模數和輻角套用運算,然後轉換回來。平方根將輻角減半並取模數的根。乘冪將輻角相乘並提高模數。以任何其他方式進行都意味著在直角座標系形式中重新推導每個恆等式,這既需要更多程式碼,又在分支切割附近較少數值穩定性。
位元運算子與您必須先檢查的溢位
Excel 2013 新增了 BITAND、BITOR、BITXOR、BITLSHIFT 和 BITRSHIFT。運算元受到限制:每個都必須是小於或等於 2^48 減 1 的非負整數,且任何分數或負數引數都是數值錯誤。該上限足夠慷慨,可以涵蓋任何逆量旗標集,同時保持在雙精確度浮點數可精確表示的範圍內,這很重要,因為 Excel 將每個數值引數都作為浮點值傳遞。
移位函式帶有唯一一個真正有害的排序規則。左移可以產生遠大於其輸入的值,如果您先執行 shl 然後在事後檢查結果,您就已經溢位了 Int64,此時測試就毫無意義了。檢查必須在移位之前進行。HotXLS 將運算元與向右移位移位量後的上限進行比較,且只有在運算元適合時它才執行實際的左移。超過 53 位元的移位大小會被直接拒絕,而負數移位只會反轉方向,因此帶有負數計數的 BITLSHIFT 行為類似於右移。該原則廣泛推廣到此單一函式之外:當存在防止溢位的防護時,它必須在輸入上執行,絕不能在旨在保護的結果上執行。
// Bitwise calls evaluate the same way through Calculate.
Sheet.Cells[3, 1].Value := Sheet.Calculate('=BITAND(13,11)'); // 9
Sheet.Cells[4, 1].Value := Sheet.Calculate('=BITLSHIFT(5,2)'); // 20
Sheet.Cells[5, 1].Value := Sheet.Calculate('=BITRSHIFT(40,3)'); // 5
未來功能與 _xlfn 名稱前綴
位元運算子和 long 清單等其他 2007 年之後的新增內容與命名配置互動,該配置與它們計算的內容無關,而與 Excel 如何儲存它們密切相關。原始的二進位工作表格式在固定表中為每個內建函式分配了一個數值插槽。在該表被凍結後發明的函式沒有插槽。為了將此類函式儲存到檔案中並讓現代 Excel 識別它,該名稱會寫入為帶有 _xlfn. 前綴,因此 BITAND 在磁碟上儲存為 _xlfn.BITAND,儘管使用者只輸入 BITAND。
問題在於該規則並不統一。一些較新的函式被給予了表插槽並以裸字寫入,而一些舊有的隱藏函式儘管年代久遠,在寫入時也不帶前綴。HotXLS 保留了哪些名稱需要前綴的明確白名單(在寫入時新增並在讀取時清除),因此您設定並讀回的公式文字始終是面向 Excel 的乾淨名稱。您設定 =BITLSHIFT(5,2),檔案保存 _xlfn.BITLSHIFT,且無論如何值都會返回為 20。前綴是一個儲存細節,絕不應該洩漏到您在程式碼中使用的公式中。
在工作表中將其組合在一起
所有這些的公用介面很小。建立一個 TXLSXWorkbook,新增一個工作表,然後透過 Cells[Row, Col].Formula 將公式寫入儲存格中並重新計算,或者使用工作表的 Calculate 方法直接評估運算式(這會針對該工作表編譯公式並傳回 Variant)。上面的範例使用 Calculate,因為它顯示了單個工程呼叫的結果,而沒有周圍的工作表狀態,但當活頁簿重新計算時,相同的函式在真實的儲存格公式中評估的結果完全相同。
要記住的部分是編碼,而不是呼叫端點。二進位字串僅在十位數且僅在超過其基數的半臨界值時才帶正負號。複數是文字,空的虛數係數是一,且剖析器跨過指數的 e。左移在移位前進行檢查。弄對這四個事實,工程系列就不再是正負號偏差驚喜的來源。
如果您要將您自己的領域數學連接到相同的引擎中,註冊處理常式和傳回值的機制在我們關於使用自訂函式擴充公式引擎的文章中介紹,而當這些公式必須按名稱而非儲存格地址跨工作表存取時,定義名稱和跨工作表公式逐步解說展示了參照如何解析。此處介紹的工程功能作為適用於 Delphi 和 C++Builder 的 HotXLS 試算表元件 的一部分出貨,同時也提供本部落格其他地方介紹的讀取、寫入和計算 API。