每一條試算表管線遲早都得問一個問題:工作簿裡存的數字,還跟產出它們的公式相符嗎。HotXLS 回答的就是這個問題。CalculateAndVerify 把整張相依圖重算進一個隔離的 overlay,把每個結果跟儲存格裡既有的快取值比對,回報所有不一致。預設什麼都不改
這件事要緊的原因是:試算表檔案在每個公式儲存格裡存兩樣東西——公式,以及某個人上次為它算出的值。Excel 會讓兩者保持同步,世界上其他東西未必。一份經手過舊程式庫、一次不完整的重算、一份被手工編輯過的 XML 部件、或一個只寫值不算值的工具的檔案,會面不改色地端出一個再也推不出自其輸入的總計,而檔案格式裡沒有任何東西標記這件事
為什麼快取值跟公式不符這麼危險?
因為在每一條普通的讀取路徑上它都是隱形的。用檢視器開檔、透過 API 讀儲存格、匯出成 CSV 或 PDF,您拿到的都是快取數字。公式就在同一個儲存格裡躺著,卻沒有人比對兩者。不一致只在有人用 Excel 打開工作簿時浮上水面——大多數設定下 Excel 載入即重算——然後上一季才簽核過的報告,總計突然對不起來
稽核的存在,就是讓這個比對變成一次刻意、有排程的操作,而不是一場意外。它相當於試算表版的檢查碼驗證:便宜到可以放進進件管線跑,而且是把無聲的資料完整性問題變成一份能動手處理的報告的唯一手段
var
Book: TXLSWorkbook;
Options: TXLSRecalcAuditOptions;
Report: TXLSCalculationAuditReport;
I: Integer;
begin
Book := TXLSWorkbook.Create(nil);
try
Book.LoadFromFile('quarterly-close.xls');
Options := TXLSRecalcAuditOptions.Default;
Options.MaxIssues := 500;
Report := Book.CalculateAndVerify(Options);
try
for I := 0 to Report.Count - 1 do
if Report[I].Kind = xlcaiCacheMismatch then
Writeln(Report[I].SheetName, '!',
Report[I].Row, ':', Report[I].Col, ' ',
Report[I].Formula,
' cached=', VarToStr(Report[I].Actual),
' recomputed=', VarToStr(Report[I].Expected));
if Report.Truncated then
Writeln('issue budget reached, raise MaxIssues');
finally
Report.Free;
end;
finally
Book.Free;
end;
end;
有三個多載,回答三個不同的問題。無參數的 CalculateAndVerify 回傳一個不一致計數,健康檢查要的就這麼多。帶 out 不一致陣列的多載把儲存格交給您。收 TXLSRecalcAuditOptions 的多載回傳完整的 TXLSCalculationAuditReport,當您不只要知道某個值不一致、還要知道稽核為什麼無法評估某個東西時,伸手拿的正是這一個
overlay,以及稽核為什麼不寫入
每個重算出的值都落進 overlay,而不是儲存格快取,而且這個 overlay 在兩套工作簿引擎裡都注入在儲存格讀取回呼的最前端。正是這個位置讓稽核自洽:B1 重算之後,相依 B1 的 C1 看到的是這一輪稽核的值,不是過期的快取值。沒有這一層,一個上游錯誤只會被回報一次就被吸收掉,每一個下游儲存格看起來都跟一個錯的輸入相安無事
重算值與快取相符的儲存格根本不進 overlay。這不是微最佳化,這是稽核跑得起的理由。一本十萬條公式、乾乾淨淨的工作簿,overlay 寫入次數是零,整輪成本的預算維持在完整重算的 1.35 倍以內——這正是「每次進件都能跑」與「一季跑一次」的差別
評估遵循一個由相依圖推導的序列拓撲順序,先把每個節點標為 dirty,所以每個儲存格都在它的輸入之後恰好計算一次。如果您要的是讓活的工作簿保持即時的增量機制,而不是稽核一份存起來的檔案,那是另一套機制,記錄在增量重算與相依圖
失敗是分類的,不是一鍋燴
稽核無法評估的儲存格,跟值不一致的儲存格,不是同一種發現,TXLSCalculationAuditIssueKind 把這些類別分得清清楚楚。xlcaiCacheMismatch 是值的不一致。xlcaiMissingFunction 與 xlcaiMissingName 表示評估器遇到了它沒實作、或無法解析的東西。xlcaiUnsupportedArguments 涵蓋支援子集之外的參數形狀。xlcaiExternalReferenceDenied 與 xlcaiExternalReferenceMissing 把政策拒絕跟工作簿缺席分開。xlcaiCircularReference、xlcaiDataTableSkipped、xlcaiParseFailure、xlcaiCancelled 與 xlcaiInternalFailure 補齊整個集合
有一個區分值得特別說,因為它反轉了一個常見假設。正值的 Excel 錯誤碼是結果,不是失敗。一個合理地算出 #DIV/0! 的儲存格,已經正確地計算完了,所以稽核把那個錯誤存進 overlay,跟其他任何值一樣與快取比對。一本塞滿刻意錯誤值儲存格的工作簿,產出零筆發現;一本自快取寫入之後某個錯誤出現或消失的工作簿,產出的恰好就是您要的那些發現
循環參照有自己的待遇。環上的節點永遠進不了拓撲順序,所以每一個都以 xlcaiCircularReference 個別回報,稽核也不去跑反覆求解器。這是刻意的唯讀契約:反覆運算有沒有開,影響的是結果碼該怎麼解讀,不是稽核做什麼。反覆評估的機制另文記錄在反覆運算與循環參照
讀懂一條失敗鏈
公式評估失敗時,只知道哪個儲存格失敗通常不夠,因為失敗往往躲在參照鏈下三層。所以每筆議題都帶著一個 Stack 字串,最外層框架先渲染,形如 Sheet1!A1 > Sheet1!B2 > Data!C7,讓報告指向真正斷掉的那個儲存格,而不是您碰巧在看的那個
記錄器有邊界。MaxStackFrames 預設 64、下限 8,留下的是最深的那條失敗鏈:失敗在某個內層框架發源時,由它記錄這條鏈,之後回溯的外層框架不會覆寫它。若有任何鏈超出預算,Report.StackTruncated 會被設起,這讓您分得清「短鏈」與「沒看全的鏈」
// 預設唯讀。ApplyResults 只在一次完全成功的稽核之後
// 才提交 overlay,而且有寫入防衛把關:稽核執行期間
// 工作簿結構若變動,提交會被拒絕
Options := TXLSRecalcAuditOptions.Default;
Options.ApplyResults := True;
Options.AbsoluteTolerance := 0; // 精確比對,讓漂移現形
Options.RelativeTolerance := 0;
Options.OnProgress := HandleProgress;
Report := Book.CalculateAndVerify(Options);
try
if Report.Applied then
Book.SaveToFile('quarterly-close-repaired.xls')
else
Writeln('not applied: ', Report.Count, ' issues blocked the commit');
finally
Report.Free;
end;
procedure THarness.HandleProgress(ASender: TObject;
ACurrent, ATotal: Integer; var ACancel: Boolean);
begin
ACancel := FUserRequestedStop; // 稽核在下一個節點邊界停下
end;
什麼時候該讓稽核修復工作簿?
只有當稽核回來時完全沒有失敗類議題——而這恰恰就是 ApplyResults 替您強制的條件。提交發生在一輪完全成功的 pass 之後、沒有被取消、並且通過一道結構防衛:二進位引擎盯著工作簿變更識別碼,OOXML 引擎對每張工作表的結構世代做快照。稽核執行期間有任何東西動了,這些結果描述的就是一份已經不存在的工作簿,提交被拒
注意這個刻意的不對稱。快取不一致不擋提交,因為它們正是提交要修復的東西。失敗類議題會擋,因為一本有些公式無法評估的工作簿會被修一半,而修一半的工作簿,比一本您明知該提防、但完好未動的工作簿更糟
容許誤差是政策決定,不是預設值
預設比對是 1E-6 的絕對容許誤差、相對容許誤差關閉,這保留了經典行為,也安靜地接受 4E-7 的漂移。這通常是對的:產檔的那個東西與目前的評估器之間,浮點數求值順序的差異會在長加總上產生這個量級的分歧,把它們當完整性發現回報只是噪音
當問題不同時,把兩個容許誤差都設成零:您想查的是某個評估器在版本之間改了行為,還是某個第三方工具用微妙不同的方式重寫了值。歸零之後,同樣的 4E-7 漂移現形了,其他所有東西也一樣。按您在問哪個問題來選容許誤差,並把選擇記在報告旁邊,因為一份沒有標注容許誤差的報告是無法解讀的
還有兩個鄰居能力補完整張圖。想知道單一條公式為什麼算出那個值,公式評估追蹤器裡的逐步視圖是對的工具。刻意要快取值原封生效、不做任何重算——例如進件路徑必須原樣重現檔案——那個模式記錄在不重算、直接讀快取公式值。稽核就坐在這兩者中間:它告訴您信任快取安不安全。它隨二進位與 OOXML 兩套引擎的 HotXLS Delphi spreadsheet component 一起出貨