Tehnični članak

Implicitno presekanje definiranih imen v HotXLS

Definirano ime, ki se sklicuje na cel stolpec, Excel prebere kot eno celico, kadar se pojavi na skalarnem mestu: =Vertical+1 v vrstici 7 pomeni "celica v vrstici 7 imena Vertical", ne celotnega obsega. HotXLS Delphi Component to implicitno presekanje izvaja od različice 2.382.4 na dveh ravneh, pri vrednotenju in pri izluščanju odvisnosti, ker je posojilna predloga s 4805 formulami pokazala, da pravilna vrednost sama po sebi ni dovolj. Ko hodec po odvisnostih ime razširi na njegov polni obseg, formula dolvodno, ki napaja katero koli celico tega obsega, sklene krog, ki ne obstaja, in TXLSXWorkbook.Recalculate zavrne celoten delovni zvezek

Predloga v vprašanju je običajen delovni zvezek za amortizacijo posojila. Ko so bile vse predpomnjene vrednosti zastrupljene na 777 in je bil zagnan celoten Recalculate, sta obe arhitekturi motorja vrnili 23, kar je lxErrorRef, koda krožnega sklica. 3842 od 4805 formul se ni ujemalo z neodvisnim pričakovanjem, B18 je držal #VALUE!, E18 je bil še vedno 777, število plačil v J7 pa je prebralo nadomestne vrednosti v nedokončanem stolpcu stanja. Za eno samo povratno kodo so se skrivali trije ločeni defekti in članek obdela vsakega z izvorno kodo, ki ga je odpravila

Zakaj skalarni sklic na ime stolpca ustvari lažni krog?

Ker graf odvisnosti pozna samo povezave in je povezava od formule do obsega s 480 vrsticami 480 povezav, od katerih ena vodi nazaj skozi celico, ki je odvisna od te formule. Vzemite =IF(TRUE,Vertical+1,0) v B1, kjer je Vertical definiran kot Inputs!$A$1:$A$2, in =B1+1 v A2. Excel ovrednoti B1 kot A1+1 in A2 kot B1+1, torej ravno verigo. Hodec, ki zabeleži B1 kot odvisen od A1:A2, naredi iz A2 predhodnika B1, A2 pa že navaja B1 kot predhodnika, in Kahnova vrsta, ki poganja inkrementalno preračunavanje v HotXLS, nikoli ne vidi, da bi katero od obeh vozlišč doseglo vhodno stopnjo nič. Iz tega vzorca so sestavljene posojilne predloge: vsaka vrstica obdobja se sklicuje na poimenovane stolpce za stanje, obrestno mero in število plačil, vsako ime sega čez celoten načrt, vsaka vrstica pa tudi piše v te stolpce. Razširite imena in graf je en sam velik močno povezan sklop. Ovrednotite jih z implicitnim presekanjem in graf je množica kratkih verig, ena na vrstico, kar je tisto, kar ECMA-376 1. del §18.17.2 opisuje za referenčni operand, porabljen tam, kjer se zahteva ena sama vrednost

Zakaj je ime stolpca v HotXLS sklenilo lažni krog: ko je Vertical definiran kot Inputs!$A$1:$A$2, hodec zabeleži B1 kot odvisen od A1:A2, medtem ko A2 že navaja B1 kot predhodnika, zato se Kahnova vrsta nikoli ne izprazni; presekanje pa zoži B1 na celico v njegovi vrstici A1 in ohrani verigo po vrsticah A2, B1, A1, ki jo Recalculate razvrsti
Razširitev imena je graf spremenila v en sam velik močno povezan sklop, vrednotenje istih formul z implicitnim presekanjem pa ga spremeni v kratke verige, po eno na vrstico načrta
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Inputs');
    Book.DefinedNames.Add('Vertical', 'Inputs!$A$1:$A$2');
    Book.DefinedNames.Add('Alias', '=Vertical');
    Sheet.Cells[1, 1].Value := 1;
    // Skalarno mesto: Vertical se skrči na A1, ker je formula v vrstici 1
    Sheet.Cells[1, 2].Formula := '=IF(TRUE,Vertical+1,0)';
    Sheet.Cells[2, 1].Formula := '=B1+1';
    // Ime, katerega definicija je drugo ime, se še vedno preseka, torej je to A2
    Sheet.Cells[2, 2].Formula := '=Alias';
    // Argument referenčnega razreda: celoten obseg se sešteje, brez presekanja
    Sheet.Cells[3, 2].Formula := '=SUM(Vertical)';
    // Vrstica 6 leži izven A1:A2, presek je prazen in IFERROR ga ujame
    Sheet.Cells[6, 2].Formula := '=IFERROR(Vertical,42)';

    if Book.Recalculate = lxOk then
    begin
      // B1 = 2, A2 = 3, B2 = 3, B3 = 4, B6 = 42
      // Pred v2.382.4 je bila ta veja nedosegljiva: B1 -> A2 -> B1 je bil krog
    end;
  finally
    Book.Free;
  end;
end;

Kako se HotXLS odloči, da je argument skalaren?

HotXLS odgovor prebere iz tabele funkcij in ne iz oblike argumenta. Vsak vnos v TXLSFormula.InitFuncHash je registriran prek THashFunc.SetValue z neobveznim nizom razredov po argumentih: 'IF' nosi '100', 'SUMIF' nosi '010', 'VLOOKUP' nosi '1011', 'SUM' pa ne nosi nobenega, zato vsi njegovi argumenti padejo na razred 0 na ravni funkcije. Nova TXLSFormula.FunctionArgumentClass(APtg, AArgument) ta bajt izpostavi prek THashFuncEntry.ArgClass, rezultat 1 pa pomeni vrednostni razred. To so isti trije razredi, ki jih [MS-XLS] §2.2.2 pripisuje žetonom operandov, in kodirnik je že bil odvisen od njih: ko zapiše sklic, izračuna ptg kot $24 + $20 * aClass, kar da PtgRef za razred 0, PtgRefV za razred 1 in PtgRefA za razred 2. Datoteka BIFF, ki jo zapiše Excel, shrani ta razred v vsakem žetonu sklica, zato lahko motor, katerega tabela se ujema s specifikacijo, odgovori na vprašanje, ali je argument skalaren, ne da bi pogledal v podatke. Srednji argument SUMIF je kriterij, torej vrednost; prvi in tretji sta obsega, torej sklica. SUMPRODUCT je registriran z razredom 2 na ravni funkcije, matrika, zato =SUMPRODUCT(Vertical,Vertical) še vedno zmnoži celoten obseg

Tri funkcije za vse po prvem argumentu ne pogledajo v svoj vnos v tabeli. IF (ptg 1), CHOOSE (ptg 100) in IFERROR (ptg 255) prevedejo naprej, kar izberejo, zato njihovi vejni argumenti podedujejo razred mesta, ki ga zaseda funkcija sama. Prav to pravilo omogoči, da se =CHOOSE(1,Vertical,0) v G2 razreši v A2, medtem ko =SUMIF(Vertical,">0",Vertical) poleg njega še vedno sešteje obe vrstici; to pravilo pa načrt amortizacije preizkusi največkrat, ker se njegove celice obdobij opirajo na IF, da preverijo, ali je posojilo še odprto

Kje HotXLS bere razrede argumentov za implicitno presekanje: IF registrira 100, SUMIF 010, VLOOKUP 1011, SUM pa nič, zato njegovi argumenti padejo na razred 0; kodirnik zapiše žetone sklica kot ptg $24 plus $20 krat razred, kar da PtgRef, PtgRefV in PtgRefA, prehodne funkcije IF, CHOOSE in IFERROR pa podedujejo razred mesta, ki ga zasedajo
Ker se tabela razredov ujema s specifikacijo, lahko motor odgovori, ali je argument skalaren, ne da bi pogledal v podatke, da pa se CHOOSE razreši v A2 poleg SUMIF, ki sešteje obe vrstici, sledi iz enega samega pravila

Prenos razreda skozi hod po odvisnostih

Izluščevalnik odvisnosti v lxCalc.pas je rekurzivni Walk po prevedenem drevesu sintakse in obstaja dvakrat, enkrat v TXLSCalculator.ExtractDependencies za graf na ravni delovnega zvezka in enkrat v ExtractWorkspaceDependencies za graf med delovnimi zvezki. Različica 2.382.4 daje obema hodcema dva dodatna parametra. AScalar se začne kot True v korenu formule, se za vsakega otroka funkcije znova izračuna iz FunctionArgumentClass in se za vejne argumente ptg 1, 100 in 255 prenese nespremenjen. ANameRoot postane True samo takrat, ko se hodec spusti v prevedeno definicijo imena, in preživi le skozi vozlišča SA_GROUP, torej oklepaje, zato ime, definirano kot =A1:A2+1, ni zamenjano za navaden obseg. Ko sta obe zastavici True v vozlišču SA_RANGE, AddResolvedRange zoži obseg z istim pomožnim elementom, ki ga uporablja vrednotenje, preden zabeleži odvisnost. Pomožni element je dovolj kratek, da ga navedemo v celoti

Odločitev IntersectNamedScalarRange, ki varuje odvisnosti imen v HotXLS: obseg, ki je že ena celica, gre skozi nespremenjen, en sam stolpec se zoži na vrstico formule, kadar CurRow pade znotraj, ena sama vrstica se zoži na stolpec formule, vse drugo, dvodimenzionalni obseg ali vrstica izven obsega, pa med vrednotenjem da #VALUE! in ne zabeleži nobene odvisnosti
Oba hodca po odvisnostih in vrednotenje kličeta isti pomožni element, zato se vrednost, ki jo formula prebere, in povezava, ki jo graf zabeleži, glede presekanega imena ne moreta nikoli razhajati
function IntersectNamedScalarRange(CurRow, CurCol: Integer;
  var Row1, Row2, Col1, Col2: Integer): Boolean;
begin
  Result := False;
  if (Row1 = Row2) and (Col1 = Col2) then Exit(True);   // že ena celica
  if (Col1 = Col2) and (CurRow >= Row1) and (CurRow <= Row2) then
  begin
    Row1 := CurRow; Row2 := CurRow;                     // en sam stolpec: vzemi to vrstico
    Exit(True);
  end;
  if (Row1 = Row2) and (CurCol >= Col1) and (CurCol <= Col2) then
  begin
    Col1 := CurCol; Col2 := CurCol;                     // ena sama vrstica: vzemi ta stolpec
    Result := True;
  end;
end;

Vse, kar pomožni element zavrne — dvodimenzionalni obseg, sklic čez več listov ali formula, katere vrstica leži izven poimenovanega stolpca — na strani vrednotenja da #VALUE!, na strani grafa pa ne zabeleži nobene odvisnosti, kar je tisto, kar Excel naredi ob praznem preseku. Stran vrednotenja živi v TXLSCalculator.GetValueItemName: ta zlušči ovoje SA_GROUP iz prevedene definicije in če je koren SA_RANGE, pokliče GetRangeInfo, preseka ter prek FGetValue pobere eno celico, namesto da bi ovrednotil celotno definicijo. Zunanji sklici ostanejo na stari poti, ker ni lokalne vrstice, proti kateri bi se presekali. Od kod izvirata shramba in obseg imena, pokriva članek o definiranih imenih in formulah med listi; tu je pomembno le, kaj motor naredi, ko se ime razreši

Zakaj je MATCH čez napol izračunan stolpec prebral 777?

Ker je argument iskalne matrike pri MATCH sklic za pregledovanje in so bili sklici za pregledovanje namenoma izključeni iz vrstnega reda vrednotenja. Članek o pregledovanju pri iskanju je predstavil TXLSDepRange.LookupScan in se končal z razdelkom "Čemu se odrečete, če pregledovalne povezave izločite iz razvrščanja": formula za iskanje se lahko izvede, preden je preračunana vsaka celica v njenem obsegu, in prebere zastarele vrednosti. V interaktivni seji se to izravna v naslednjem prehodu. V paketnem preračunu zastrupljene predloge se ne, in PaymentCount, definiran kot =MATCH(0.01,Balances,-1)+1, je prebral nadomestne vrednosti 777, ki so še sedele v stolpcu stanja, ter vrnil število obdobij, ki ni moglo biti pravilno

TXLSDepGraph.TopoOrder zdaj obravnava pregledovalne povezave kot mehke povezave razvrščanja. Ob trdi vhodni stopnji hrani matriko ScanInDeg, ki šteje umazane pregledovalne predhodnike na vozlišče in jo zmanjšuje, ko se ti predhodniki oddajo, pri čemer uporablja sezname ScanPrecedents, ScanDependents in ScanPrecedentCount, ki jih je prejšnja sprememba že shranila. V vsaki ponovitvi Kahnova vrsta preišče svoje okno pripravljenih vozlišč in poišče prvo vozlišče z ScanInDeg nič ter ga zamenja na glavo; če vsako pripravljeno vozlišče še čaka na pregledovalnega predhodnika, se glava pobere v svojem stabilnem vrstnem redu. Pregledovalne povezave nikoli ne vstopijo v trdo vhodno stopnjo, zato je samoreferenčni VLOOKUP čez lasten stolpec še vedno zakonit, iskanje, ki pa bi lahko počakalo na dokončljivega predhodnika, zdaj počaka. Regresijski test, ki to pripne, LookupScan_WaitsForDirtyFormulaValues, zastrupi tri celice stanja na 777 in pričakuje, da se PaymentCount vrne kot 3, nato obrne vhod na nič in pričakuje, da =IFERROR(PaymentCount,99) vidi #N/A in vrne 99

Od kod je prišlo odrezovanje na štiri decimalke?

Iz aritmetike Variant v Delphiju in samo na gnezdenih mestih. Binarni operatorji v TXLSCalculator.GetValueItem so že prej kopirali vrhnji + ali - v dva lokalna Double, zato je bil =B1-A1 v redu. Znotraj =IF(TRUE,B1-A1,0) se je isto odštevanje izvedlo kot Value := Value - SubValue nad dvema Variantoma, in ko je bil en operand celična vrednost Int64 drugi pa Double, je bil rezultat, ki smo ga opazili, Currency, fiksna točkovna vrsta s štirimi decimalnimi mesti, zato se je 1066.1854641400994 minus 120 vrnilo odrezano na štiri decimalke. V načrtu, kjer se vsako plačilo obrestuje iz prejšnje vrstice, ta napaka prehodi stotine obdobij, preden doseže vsote

// TXLSCalculator.GetValueItem, veja za binarno aritmetiko (lxCalc.pas)
if VarIsNull(Value) then Value := 0;
if VarIsNull(SubValue) then SubValue := 0;
// Mešana aritmetika Variant Int64/Double se lahko poviša v Currency.
// Aritmetika preglednice mora ohraniti natančnost s plavajočo vejico.
if VarIsNumeric(Value) then Value := Double(Value);
if VarIsNumeric(SubValue) then SubValue := Double(SubValue);

Preverjanje se izvede pred SA_ADD, SA_SUB, SA_MUL in SA_DIV enako, regresijski test Arithmetic_MixedInt64AndDoubleKeepsPrecision pa shrani Int64(120) v A1 in 1066.1854641400994 v B1 ter nato preveri gnezdeno razliko in vsoto na 1E-10, zmnožek in količnik pa na 1E-8 oziroma 1E-12. HotXLS ne trdi, da pozna vsako pravilo povišanja, ki ga RTL uporablja za mešane vrste Variant skozi različne različice prevajalnika; trdi, da je aritmetika preglednice dvojna natančnost IEEE, in zdaj oba operanda spremeni v double, preden ju vidi operator, s čimer vprašanje odstrani

Kaj popravek zagotavlja in česa ne

Po različici 2.382.4 obe arhitekturi motorja za zastrupljeno predlogo vrneta lxOk, vseh 4805 predpomnjenih vrednosti se ujema z neodvisnim pričakovanjem po vrsticah znotraj 1E-7, držijo pa tudi trditve, da so bili predpomnilniki res zastrupljeni, da je hash vira nespremenjen in da je vsaka formula še vedno prisotna. Za to ni bila omogočena nobena ponovitev in nobena koda napake ni bila zadušena. Pravi krog skozi ime, =B1 v A1, kjer B1 še vedno bere Vertical, še vedno vrne napako, in test NamedScalarRanges_IntersectWithoutFalseCycles se konča prav s to trditvijo

Meje velja povedati naravnost. Implicitno presekanje velja samo za ime, katerega prevedena definicija je po odstranitvi oklepajev enostolpčni ali enovrstični obseg na enem listu; dvodimenzionalno ime na skalarnem mestu da #VALUE!, tako kot v Excelu, funkcija, ki je tabela ne pozna, pa od FunctionArgumentClass dobi razred 0, zato se njeni argumenti imen še vedno razširijo v celoti. Mehko razvrščanje je preference in ne jamstvo: krog samo iz pregledovalnih povezav se še vedno ovrednoti v stabilnem vrstnem redu in prebere, kar je predpomnjeno, kar je vedenje, ki ga je članek o pregledovanju pri iskanju sprejel namenoma. Rezultat za celotno predlogo je preverjen proti neodvisni skripti pričakovanj in ne proti drugemu motorju preglednic, ker referenčna pisarniška zbirka ni dokončala preračuna izvirne predloge v 60-sekundnem proračunu. HotXLS je izvorna komponenta za preglednice za Delphi in C++Builder, ki bere, preračunava in piše XLS, XLSX, ODS in CSV brez nameščenega Excela; presekanje imen, tabela razredov argumentov in mehko razvrščanje pregledovanja veljajo za vse formate, ker je motor za izračun skupen, trenutna pokritost funkcij pa je našteta na strani izdelka HotXLS Delphi spreadsheet component