HotXLS, det inbyggda Excel-biblioteket för Delphi och C++Builder, utför inkrementell formelomräkning genom TXLSXWorkbook.Recalculate. Det första anropet bygger en formelberoendegraf och utvärderar varje formelcell; varje senare anrop återutvärderar endast de celler som påverkats av värdeskrivningar sedan förra körningen, i topologisk ordning, i ett enda svep vars kostnad är proportionell mot antalet smutsiga celler snarare än storleken på arbetsboken
Det där designbeslutet är skillnaden mellan en finansiell modell som svarar på ett redigerat antagande på några millisekunder och en som låser sig i sekunder. Om du genererar rapporter där en handfull indataceller matar tusentals efterföljande formler, förklarar resten av den här artikeln vad grafen gör, vilka funktioner som väljer bort inkrementell räkning samt hur cirkelreferenser rapporteras istället för att loopa i all evighet
Varför beräknar ändring av en cell om hundratusen formler?
En naiv formelmotor har inget minne av vem som beror på vem, så dess enda säkra drag efter en redigering är att utvärdera allt igen. Värre är att den klassiska rekursiva strategin — när formel A refererar till formel B, utvärdera B på stående fot — återutvärderar refererade celler villkorslöst, och ignorerar eventuella cachelagrade värden. En kedja av n formler där var och en refererar till den föregående kostar O(n²) utvärderingar per fullständig körning, och en cirkelreferens skickar rekursionen över kanten. Varje kalkylbladsutvecklare som har kopplat en kaskadmodell till en rekursiv utvärderare har sett båda felscenarierna inträffa
Excel löste detta för decennier sedan med sin beräkningskedja: en ordning av formelceller som upprätthålls så att en redigering markerar en liten uppsättning celler som smutsiga och motorn går bara igenom den påverkade änden av kedjan. HotXLS tillämpar samma idé som en explicit beroendegraf, byggd en gång från de kompilerade formelträden och återanvänd under omräkningskörningar. Poängen är inte att vara smart; det är att omräkningskostnaden ska följa storleken på din redigering, inte storleken på din arbetsbok
Hur beroendegrafen gör en redigering till en enda körning
Beroendegrafen i HotXLS ger varje formelcell en nod, med kanter som går från föregångare till beroende. När din kod skriver ett cellvärde registrerar arbetsboken cellen som smutsig; när Recalculate körs sprids smutsigheten längs kanterna till varje efterföljande formel, och den smutsiga delgrafen utvärderas exakt en gång i topologisk ordning med Kahns algoritm. Eftersom en formel aldrig besöks före dess föregångare behöver varje nod en enda utvärdering — det är det som gör körningen O(dirty)
Topologisk ordning också löser rekursionsproblemet vid dess rot. Under en omräkningskörning växlar motorn till ett dedikerat läge där varje referens till en annan formelcell läser den cellens cachelagrade värde direkt istället för att återutvärdera den — ordningen garanterar att cachen redan är uppdaterad. Samma mekanism gör att en referenscykel inte kan utlösa obegränsad rekursion: ingenting under körningen går någonsin in i utvärderaren för en angränsande cell igen
var
Book: TXLSXWorkbook;
Inputs, Model: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Inputs := Book.Sheets.Add('Inputs');
Model := Book.Sheets.Add('Model');
Inputs.Cells[2, 2].Value := 0.05; // tillväxtantagande
Model.Cells[2, 2].Formula := 'Inputs!B2*1000'; // XLSX-formler har inget inledande '='
Model.Cells[3, 2].Formula := 'B2*(1+Inputs!B2)';
// ... tusentals fler rader som kaskaderar från samma antagande ...
Book.Recalculate; // första anropet: bygger grafen, fullständig utvärdering
Inputs.Cells[2, 2].Value := 0.07; // en redigering markerar en cell som smutsig
Book.Recalculate; // andra anropet: endast den efterföljande kedjan körs
finally
Book.Free;
end;
end;
Varje resultat landar i cellens cachelagrade Value, så efter att Recalculate har returnerat läser du utdata på samma sätt som du läser vilken annan cell som helst. I en rapportgenereringsloop är mönstret exakt koden ovan: läs in eller bygg modellen en gång, växla sedan mellan att skriva några indataceller och anropa Recalculate, och betala endast för de formler som faktiskt beror på det som ändrades
Vilka Excel-funktioner tvingar fram omräkning vid varje körning?
HotXLS behandlar NOW, TODAY, RAND, OFFSET och INDIRECT som volatila: alla formler som innehåller någon av dem utvärderas på nytt vid varje Recalculate-körning, oavsett om något uppströms har ändrats eller inte. De tre första är volatila av samma anledning som de är i Excel — deras resultat beror på utvärderingstillfället, inte på andra celler. OFFSET och INDIRECT är volatila av en finare anledning: cellerna de läser beräknas vid körning, så grafen kan inte veta statiskt vilka kanter den ska rita för dem
Samma konservativa regel gäller för referenser som grafbyggaren inte kan ringa in till en enda rektangel. En formel som går via ett namngivet intervall med flera områden (multi-area), eller en som refererar till en extern arbetsbok, nedgraderas på samma sätt till volatil och omvärderas vid varje körning. Policyn är avsiktlig: en extra utvärdering kostar lite tid, men en saknad beroendekant innebär ett tyst gammalt värde i en levererad rapport, och det är det mycket värre felet. Om din modell stöder sig på namn med arbetsbokens räckvidd (workbook-scoped), täcker följeslagsartikeln om definierade namn och formler över blad hur namn med ett enda område löses upp — dessa deltar i grafen normalt
Den praktiska vägledningen följer direkt. Håll heta sökvägar i en stor modell på vanliga cell- och intervallreferenser där grafen kan göra sitt jobb, och sätt OFFSET och INDIRECT i karantän till de få ställen som verkligen behöver dynamisk adressering. En modell med tusen volatila formler kör om dessa tusen vid varje körning oavsett hur liten redigeringen var — exakt det beteende Excel-användare känner igen från arbetsböcker som ”beräknas om vid varje knapptryckning”
Hur rapporterar HotXLS cirkelreferenser?
TXLSXWorkbook.Recalculate returnerar lxOk vid en ren körning och lxErrorRef när den upptäcker en referenscykel. Cykelmedlemmar identifieras under den topologiska sorteringen — de är noderna som Kahns algoritm aldrig kan släppa — och de hoppas över istället för att loopas: deras cachelagrade värden förblir vad de var, medan alla formler utanför cykeln fortfarande utvärderas normalt i ordning. Ditt anropsställe får en bestämd felkod istället för en låsning
case Book.Recalculate of
lxOk:
SaveReport(Book);
lxErrorRef:
// en referenscykel finns; cykelmedlemmar behöll sina tidigare
// cachelagrade värden och allt utanför cykeln är uppdaterat
LogWarning('Circular reference detected - review model inputs');
end;
Att hitta vilka celler som bildar cykeln är ett felsökningsjobb, och spåraren av formelutvärdering är rätt verktyg för det: spåra den misstänkta formeln så blir referenskedjan som viker sig tillbaka på sig själv synlig steg för steg. Cykler i verkliga modeller är nästan alltid ett författarfel — en sammanfattningsrad som av misstag inkluderas i sitt eget SUM-intervall — så en tydlig felkod vid omräkningstillfället är precis vad du vill ha
Matrisformler, spårning av smutsiga celler och när grafen byggs om
CSE matrisformler får en nod för hela den förankrade rektangeln, inte en nod per cell. Rotformeln utvärderas en gång per körning; den resulterande matrisen skrivs direkt till varje medlemscell, och en formel som refererar till en cell inuti det förankrade intervallet — inte bara det övre vänstra ankaret — plockar upp en beroendekant från den rotnoden. Skalära resultat sprids över rektangeln på det sätt som Excels äldre matrissemantik föreskriver
Spårning av smutsiga celler krokar in i de vanliga egenskapstilldelningarna, så inget ändras i din kod. Att skriva Value i en cell meddelar arbetsboken och markerar beroende celler som smutsiga; att tilldela en ny Formula är en strukturell förändring, så det markerar hela grafen som inaktuell, och nästa Recalculate bygger om den innan utvärdering. Att lägga till, ta bort eller flytta blad ogiltigförklarar också grafen, eftersom nodidentiteten kodar bladindexet. När ingen graf är aktiv — en arbetsbok där du aldrig anropar Recalculate — kostar krokarna en enda nil-kontroll per tilldelning, så vanliga läs- och skrivarbetsbelastningar påverkas inte
En gräns värd att nämna ärligt: grafen spårar beroenden mellan celler, så en användardefinierad funktion registrerad via OnUserFunction utvärderas på nytt när cellerna som matar dess argument ändras, precis som alla andra formler. Om du utökar motorn på det sättet går artikeln om anpassade funktioner i formelmotorn för HotXLS igenom återanropskontraktet och hur argumentvärden levereras
Inkrementell omräkning är en del av den normala XLSX-motorn i HotXLS Delphi Excel Component, tillsammans med formelberäknaren, definierade namn och import/export-pipelinen den accelererar. Om din Delphi- eller C++Builder-applikation underhåller levande modeller — prissättningsblad, konsolideringsarbetsböcker, rapportkaskader — är Recalculate skillnaden mellan att beräkna om en arbetsbok och att beräkna om en redigering