Tehnički članak

HotXLS pretrage lookup-a i lažne kružne reference

Stavite =VLOOKUP(A1,B:B,1) u ćeliju u koloni B i Excel ga izračunava bez prigovora. Dajte istu radnu svesku motoru preračunavanja grafa zavisnosti i verovatno ćete dobiti grešku kružne reference, jer formula zavisi od opsega koji sadrži formulu. HotXLS je tačno to izveštavao do v2.361.98. Popravka nije poseban slučaj za opsege cele kolone; to je razlika između dve vrste grane zavisnosti koje motor tabele traži, a običan usmeren graf nema

Argument lookup-niza familije pretrage, LOOKUP, MATCH, HLOOKUP, VLOOKUP, XLOOKUP i XMATCH, sada je označen kao referenca skeniranja. Referenca skeniranja i dalje seje prljavost, pa uređivanje ćelije unutar opsega preračunava formulu, ali nikad ne doprinosi detekciji ciklusa niti uređenju evaluacije. Pravi ciklusi se i dalje nalaze; lažni su nestali

Zašto Excel dopušta da opseg pretrage sadrži formulu?

Zato se taj argument ne troši na način aritmetičkog operanda. Familija pretrage skenira opseg za keširane vrednosti i vraća podudaranje; ne traži da je opseg prvo evaluiran do kraja. Excel tretira preklapajući-sam-sobom opseg pretrage kao čitanje onoga što te ćelije trenutno drže, što je ista semantika koju primenjuje na bilo koju neiterativnu radnu svesku: ćelije koje nisu preračunate u ovom prolazu doprinose svoju poslednju izračunatu vrednost

Reference cele kolone čine ovo uobičajenim slučajem, a ne egzotičnim. B:B je idiomatski način da se napiše "cela tabela pretrage" u listu gde se redovi dodaju, i svaka formula koja živi u koloni B tada je unutar svog sopstvenog opsega pretrage. Finansijski modeli, listovi usaglašavanja i revizorske radne sveske ovo rade stalno, obično bez da itko primeti da se opseg preklapa

Ćelija B7 drži VLOOKUP(A1,B:B,1) unutar svog sopstvenog opsega pretrage cele kolone B:B, samopreklapanje koje Excel računa iz keširanih vrednosti bez prigovora
Opsezi pretrage cele kolone čine samopreklapanje normalnim slučajem u finansijskim modelima i revizorskim radnim sveskama, a ne egzotičnim ćoškom

Šta graf zavisnosti radi sa istom formulom

HotXLS preračunava inkrementalno, što traži pravi graf zavisnosti: čvorove za ćelije, grane za reference, topološki redosled za evaluaciju i prolaz jako povezanih komponenti za razvrstavanje ciklusa. Ta mašinerija opisana je u članku o inkrementalnom preračunavanju, i upravo je to razlog zašto se lažno pozitivno pojavilo

Izvucite zavisnosti iz =VLOOKUP(A1,B:B,1) u ćeliji B7 i drugi argument daje opseg koji sadrži B7 sam. Graf sada ima samu-petlju. Ulazni stepen tog čvora nikad ne stigne do nule, pa topološki prolaz nikad ne može zakazati, i prolaz komponenti razvrstava ga kao ciklus. Motor rasuđuje ispravno o grafu koji mu je dat. Graf je pogrešan model, jer koduje jednu vrstu grane gde tabela ima dve

Opseg pretrage B:B daje čvoru grafa B7 samu-petlju, pa ulazni stepen nikad ne stigne do nule i HotXLS pre v2.361.98 izveštavao je lažnu kružnu referencu
Motor preračunavanja rasuđivao je ispravno o grafu koji mu je dat; graf je bio pogrešan model za tabelu

Dve klase grana, jedan graf

Izmena dodaje zastavicu zapisu razrešene reference, TXLSDepRange.LookupScan, koju izvodič zavisnosti postavlja kada obilazi argument lookup-niza jedne od šest funkcija. Nizvodno, grane potekle iz tih referenci čuvaju se odvojeno od običnih grana: čvor grafa drži popise ScanDependents i ScanPrecedents pored svojih normalnih popisa zavisnih i prethodnih

Razdvajanje je ono što čini semantiku ispravnom. Grane skeniranja prelaze se propagacijom prljavosti, pa uređivanje bilo gde u B:B i dalje označava B7 prljavim i B7 se preračunava. Grane skeniranja nikad se ne računaju u ulazni stepen i nikad ne ulaze u graditelja komponenti, pa ne mogu stvoriti topološki zastoj niti biti razvrstane kao ciklus. Obe implementacije grafa u biblioteci, klasični graf po radnoj svesci i graf radnog prostora preko sveski koji nosi analizu komponenti, menjane su zajedno; pustiti ih da odstanu proizvelo bi radnu svesku koja se preračunava drugačije zavisno od toga da li je otvorena sama ili kao deo radnog prostora

Grane skeniranja iz TXLSDepRange.LookupScan voze propagaciju prljavosti u ScanPrecedents i ScanDependents ali nikad se ne računaju u ulazni stepen ili cikluse
Uređivanja unutar B:B i dalje označavaju formulu prljavom, ali grane skeniranja ne mogu zaglaviti topološki prolaz ni proizvesti ciklus
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;
    // Opseg pretrage pokriva kolonu B, i ova formula živi u njoj
    Sheet.Cells[7, 2].Formula := 'VLOOKUP(A1,B:B,1)';

    case Book.Recalculate of
      lxOk:
        // Pre v2.361.98 ova grana bila je nedostižna za ovaj list
        SaveReport(Book);
      lxErrorRef:
        LogWarning('Genuine circular reference - review model inputs');
    end;
  finally
    Book.Free;
  end;
end;

Šta odustajete isključivanjem grana skeniranja iz uređenja

Tačno jedna stvar, i vredi je izjaviti jasno umesto kriti. Pošto grane skeniranja ne učestvuju u topološkom redosledu, formula pretrage može biti evaluirana u istom prolazu pre nego što neke ćelije u svom opsegu pretrage budu preračunate, i tada će čitati njihove prethodne vrednosti. Rezultat konvergira na sledećem preračunavanju

To je prihvatljivo jer je to ono što Excel radi. Za radnu svesku bez uključenog iterativnog računanja, sopstveni odgovor Excel-a za vrednost još nepreračunatu u tekućem prolazu jeste poslednja izračunata vrednost, pa motor koji reprodukuje ovo ponašanje poklapa referentnu implementaciju umesto da je aproksimira. Ako vam treba istinski konvergiran odgovor preko samoreferencijalnog modela, mehanizam za to je iterativno računanje sa eksplicitnom granicom iteracija, pokriveno u članku o iterativnom računanju, i primenjuje se na prave cikluse umesto na preklapanja skeniranja

Opasnost regresije koja se krije unutar popravke

Dodavanje LookupScan u TXLSDepRange unelo je rizik koji nema ništa sa pretragama a sve sa Pascal-om. TXLSDepRange je neupravljani zapis, pa lokalna promenljiva tog tipa nije nula-inicijalizovana. Svako mesto u bazi koda koje ga gradi ručno, uključujući blokove zavisnosti tabela podataka i nekoliko test pomagača, moralo je biti ažurirano da postavi novo polje eksplicitno. Preskočite jedno i bilo koji bajt koji se slučajno nalazio na steku odlučuje da li se ta referenca tretira kao grana skeniranja, što proizvodi grešku preračunavanja koja se pojavljuje i nestaje sa nevezanim izmenama koda

// Novo Boolean polje u neupravljanom zapisu čini svako mesto
// ručne konstrukcije latentnom greškom. Dva bezbedna idioma:
var
  R: TXLSDepRange;
begin
  FillChar(R, SizeOf(R), 0);      // nula sve, pa popunite
  R.Sheet1 := SheetIndex;
  R.Sheet2 := SheetIndex;
  R.Row1 := Row; R.Col1 := Col;
  R.Row2 := Row; R.Col2 := Col;

  // ili postavite svako polje, uključujući novo, na svakom mestu
  R.LookupScan := False;
end;

Opšte pravilo koje je ovo zaradilo: dodavanje polja zapisu koji se konstruiše na steku na više od šake mesta promena je višeg rizika nego što izgleda, i kompajler vam neće pomoći naći mesta. Ako je zapis dostižan iz vruće putanje, dajte prednost pomagaču koji ga kompletno inicijalizuje nad verovanjem da će se svako mesto poziva ažurirati

Razlikovanje pravog ciklusa od preklapanja skeniranja

Ništa kod ove izmene ne slabi detekciju ciklusa. =B7+1 u B7 i dalje je ciklus, lanac od tri formule koji se zatvara na sebe i dalje je ciklus, i oba se i dalje izveštavaju kroz rezultat preračunavanja sa članovima ciklusa koji zadržavaju prethodne keširane vrednosti dok sve van ciklusa ostaje tekuće. Ono što se promenilo jeste samo to da argument lookup-niza više ne proizvodi cikluse koje Excel ne vidi

Ako revizionirate radnu svesku i želite znati koje reference je motor zapravo razrešio i u kojem redosledu, tracer evaluacije je alat za to; članak o tracer-u evaluacije formula pokriva kako čitati njegov izlaz. HotXLS je nativna Delphi i C++Builder komponenta tabele koja čita i piše XLS, XLSX, ODS i CSV bez instaliranog Excel-a, i motor preračunavanja isti je na svakom formatu; trenutna pokrivenost funkcija i motora navedena je na stranici proizvoda HotXLS Delphi spreadsheet component