Teknisk artikel

HotXLS-uppslagsskanningar och falska cirkelreferenser

Lägg =VLOOKUP(A1,B:B,1) i en cell i kolumn B och Excel beräknar den utan klagan. Mata samma arbetsbok till en beroendegrafs-omberäkningsmotor och du får sannolikt ett cirkelreferensfel, eftersom formeln beror på ett område som innehåller formeln. HotXLS rapporterade exakt det till v2.361.98. Fixen är inte ett specialfall för helkolumnsområden; den är en distinktion mellan två slags beroendekanter som en kalkylbladsmotor behöver och en vanlig riktad graf inte har

Uppslagsarray-argumentet av uppslagsfamiljen, LOOKUP, MATCH, HLOOKUP, VLOOKUP, XLOOKUP och XMATCH, är nu märkt som en skankreferens. En skankreferens sår fortfarande orenhet, så att redigera en cell inuti området räknar om formeln, men den bidrar aldrig till cykeldetektering eller till utvärderingsordning. Äkta cykler hittas fortfarande; de falska är borta

Varför tillåter Excel att ett uppslagsområde innehåller formeln?

För det argumentet konsumeras inte som en aritmetisk operand gör. Uppslagsfamiljen skannar området efter cachade värden och returnerar en träff; den kräver inte att området har utvärderats till slut först. Excel behandlar ett självöverlappande uppslagsområde som att läsa vadhelst de cellerna för tillfället håller, vilket är samma semantik den tillämpar på valfri icke-iterativ arbetsbok: celler som inte räknats om i detta pass bidrar med sitt senast beräknade värde

Helkolumnreferenser gör detta till det vanliga fallet i stället för ett exotiskt. B:B är det idiomatiska sättet att skriva ”hela uppslagstabellen” i ett ark där rader läggs till, och varje formel som bor i kolumn B är då inuti sitt eget uppslagsområde. Finansiella modeller, avstämningsark och granskningsarbetsböcker gör detta ständigt, vanligtvis utan att någon lägger märke till att området överlappar

Cell B7 bär VLOOKUP(A1,B:B,1) inuti sitt eget helkolumns-uppslagsområde B:B, en självöverlappning Excel beräknar från cachade värden utan klagan
Helkolumns-uppslagsområden gör självöverlappning till det normala fallet i finansiella modeller och granskningsarbetsböcker, inte ett exotiskt hörn

Vad en beroendegraf gör med samma formel

HotXLS räknar om inkrementellt, vilket kräver en riktig beroendegraf: noder för celler, kanter för referenser, en topologisk ordning för utvärdering och ett pass för starkt sammanhängande komponenter för att klassificera cykler. Det maskineriet beskrivs i artikeln om inkrementell omberäkning, och det är precis varför den falska positiva dök upp

Extrahera beroenden ur =VLOOKUP(A1,B:B,1) i cell B7 och det andra argumentet ger ett område som innehåller B7 själv. Grafen har nu en självloop. Ingraden för den noden når aldrig noll, så det topologiska passet kan aldrig schemalägga den, och komponentpasset klassificerar den som en cykel. Motorn resonerar korrekt om grafen den fick. Grafen är fel modell, eftersom den kodar en kanttyp där kalkylbladet har två

Uppslagsområdet B:B ger grafnoden B7 en självloop, så ingraden når aldrig noll och HotXLS före v2.361.98 rapporterade en falsk cirkelreferens
Omberäkningsmotorn resonerade korrekt om grafen den fick; grafen var fel modell för ett kalkylblad

Två kantklasser, en graf

Ändringen lägger till en flagga till den upplösta referensposten, TXLSDepRange.LookupScan, som beroendeextraktorn sätter när den går igenom uppslagsarray-argumentet för en av de sex funktionerna. Nedströms lagras kanter med källa i de referenserna åtskilda från vanliga kanter: grafnoden håller ScanDependents- och ScanPrecedents-listor jämte sina normala dependent- och precedentlistor

Separationen är det som gör semantiken rätt. Skankanter traverseras av smutsspridning, så en redigering var som helst i B:B märker fortfarande B7 som smutsig och B7 räknas om. Skankanter räknas aldrig in i ingraden och går aldrig in i komponentbyggaren, så de kan inte skapa en topologisk dödläge och kan inte klassificeras som en cykel. Båda grafimplementeringarna i biblioteket, den klassiska per-arbetsboksgrafen och den kors-arbetsboks arbetsytegrafen som bär komponentanalysen, ändrades tillsammans; att låta dem drifta skulle ge en arbetsbok som räknar om olika beroende på huruvida den öppnades ensam eller som del av en arbetsyta

Skankanter från TXLSDepRange.LookupScan driver smutsspridning in i ScanPrecedents och ScanDependents men räknas aldrig in i ingrad eller cykler
Redigeringar inuti B:B märker fortfarande formeln som smutsig, ändå kan skankanter inte skapa dödläge i det topologiska passet eller tillverka en cykel
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Ledger');
    Sheet.Cells[1, 1].Value := 'ACC-4471';
    Sheet.Cells[1, 2].Value := 1200.00;
    // Uppslagsområdet täcker kolumn B, och denna formel bor i den
    Sheet.Cells[7, 2].Formula := 'VLOOKUP(A1,B:B,1)';

    case Book.Recalculate of
      lxOk:
        // Före v2.361.98 var denna gren onåbar för detta ark
        SaveReport(Book);
      lxErrorRef:
        LogWarning('Genuine circular reference - review model inputs');
    end;
  finally
    Book.Free;
  end;
end;

Vad du ger upp genom att utesluta skankanter från ordningen

Exakt en sak, och den är värd att ange blankt i stället för att döljas. Eftersom skankanter inte deltar i den topologiska ordningen kan en uppslagsformel utvärderas i samma pass innan vissa celler i dess uppslagsområde har räknats om, och den kommer då läsa deras tidigare värden. Resultatet konvergerar vid nästa omberäkning

Det är acceptabelt eftersom det är vad Excel gör. För en arbetsbok utan iterativ beräkning aktiverad är Excels eget svar på ett värde som ännu inte räknats om i aktuellt pass det senast beräknade värdet, så en motor som återskapar detta beteende matchar referensimplementeringen i stället för att approximera den. Om du behöver ett genuint konvergerat svar över en självreferentiell modell är mekanismen för det iterativ beräkning med en uttrycklig iterationsgräns, som tas upp i artikeln om iterativ beräkning, och den gäller äkta cykler i stället för skannöverlappningar

Regressionsfaran som gömmer sig inuti fixen

Att lägga till LookupScan till TXLSDepRange introducerade en risk som inte har något med uppslag att göra och allt med Pascal att göra. TXLSDepRange är en ohanterad post, så en lokal variabel av den typen är inte nollinitierad. Varenda plats i kodbasen som bygger en för hand, inklusive datatabellens beroendeblock och flera testhjälpare, måste därför uppdateras att sätta det nya fältet uttryckligen. Missa en och det byte som råkade ligga på stacken avgör huruvida den referensen behandlas som en skankant, vilket ger en omberäkningsbugg som visar sig och försvinner med orelaterade kodändringar

// Ett nytt Boolean-fält i en ohanterad post gör varje manuell
// konstruktionsplats till en latent bugg. Två säkra idiom:
var
  R: TXLSDepRange;
begin
  FillChar(R, SizeOf(R), 0);      // nollställ allt, fyll sedan i
  R.Sheet1 := SheetIndex;
  R.Sheet2 := SheetIndex;
  R.Row1 := Row; R.Col1 := Col;
  R.Row2 := Row; R.Col2 := Col;

  // eller sätt vart och ett fält, inklusive det nya, på varje plats
  R.LookupScan := False;
end;

Den allmänna regeln detta förtjänade: att lägga till ett fält till en post som konstrueras på stacken på fler än ett handfull platser är en högre-risk ändring än den ser ut, och kompilatorn hjälper dig inte hitta platserna. Om posten är nåbar från en het väg, föredra en hjälpare som initierar den helt framför att lita på att varje anropsplats uppdateras

Att skilja en äkta cykel från en skannöverlappning

Ingenting vid denna ändring försvagar cykeldetekteringen. =B7+1 i B7 är fortfarande en cykel, en kedja av tre formler som sluter sig på sig själv är fortfarande en cykel, och båda rapporteras fortfarande genom omberäkningsresultatet med cykelmedlemmarna som behåller sina tidigare cachade värden medan allt utanför cykeln förblir aktuellt. Det som ändrades är bara att uppslagsarray-argumentet inte längre tillverkar cykler som Excel inte ser

Om du granskar en arbetsbok och vill veta vilka referenser motorn faktiskt löste upp och i vilken ordning är utvärderingsspåraren verktyget för det; artikeln om utvärderingsspåraren för formler täcker hur man läser dess utmatning. HotXLS är en nativ Delphi- och C++Builder-kalkylbladskomponent som läser och skriver XLS, XLSX, ODS och CSV utan Excel installerat, och omberäkningsmotorn är densamma på varje format; den aktuella funktions- och motortäckningen listas på produktsidan för HotXLS Delphi spreadsheet component