Teknik Makale

Delphi'de HotXLS Tanımlı Adlar için Örtük Kesişim

Bütün bir sütuna başvuran bir tanımlı ad, skaler bir konumda göründüğünde Excel tarafından tek bir hücre olarak okunur: 7. satırdaki =Vertical+1, Vertical'in 7. satırdaki hücresi demektir; bütün alan değil. HotXLS Delphi Component o örtük kesişimi iki düzeyde uygular: değerlendirme sırasında ve bağımlılık çıkarma sırasında; çünkü değeri doğru almak yeterli değildir. Adı tam alanına genişleten bir bağımlılık yürüyücüsü, o alanın bir hücresini besleyen her aşağı akış formülünün var olmayan bir döngüyü kapatmasına izin verir ve TXLSXWorkbook.Recalculate sonra bütün çalışma kitabını döngüsel olarak reddetmek zorunda kalır

4805 formüllü bir stok kredisi amortizasyon çalışma kitabı, bütün bunları işe koyan stres vakasıdır. Bayat önbelleklerden doğru şekilde yeniden hesaplamak üç ayrı mekanizmaya yaslanır: adlar için örtük kesişim, lookup taramalarının sıralanışı ve iç içe aritmetikte kullanılan sayısal tür. Herhangi birini yanlış alınca belirti ya lxErrorRef — döngüsel referans kodu — olur ya da bağımsız bir satır satır hesaptan sapan toplamlar olur. Bu yazı her mekanizmayı, ilgili kaynakla birlikte gezer

Bir sütun adına yapılan skaler başvuru neden yanlış bir döngü yaratıyor?

Çünkü bir bağımlılık grafiği yalnızca kenarları bilir ve bir formülden 480 satırlık bir alana giden tek bir kenar aslında 480 kenardır; bunlardan biri, formüle bağımlı bir hücreden geri döner. B1'de =IF(TRUE,Vertical+1,0) olsun, Vertical adı Inputs!$A$1:$A$2 olarak tanımlı olsun ve A2'de =B1+1 yazsın. Excel, B1'i A1+1, A2'yi B1+1 olarak değerlendirir; düz bir zincir. B1'i A1:A2'ye bağımlı olarak kaydeden bir yürüyücü A2'yi B1'in öncülü yapar; A2 zaten B1'i öncül olarak listeliyordur ve HotXLS'te artımlı yeniden hesaplama işini yürüten Kahn kuyruğu iki düğümün de in-degree değerinin sıfıra indiğini hiç görmez. Kredi şablonlarının dokusu tam olarak bu desendir: her dönem satırı bakiye, faiz oranı ve ödeme sayısı için adlandırılmış sütunlara başvurur, her ad tüm planı kapsar ve her satır ayrıca o sütunlara yazar. Adları genişletirseniz grafik tek bir dev strongly connected component olur. Aynı formülleri örtük kesişimle değerlendirirseniz grafik, her satıra bir tane düşen kısa zincirler kümesine dönüşür; ECMA-376 Part 1 §18.17.2'nin tek bir değerin gerektiği yerde tüketilen bir referans operandı için tarif ettiği davranış da budur

Bir sütun adının genişletilmesinin neden yanlış döngü kapattığını gösteren şema: Vertical, Inputs!$A$1:$A$2 olarak tanımlıyken B1'i A1:A2'ye bağımlı kaydeden bir yürüyücü A2'yi B1'e öncü yapar; A2 de hâlihazırda B1'i öncü saydığından Kahn kuyruğu hiç boşalmaz; kesişim ise B1'i satır hücresi A1'e daraltır ve Recalculate'ın sıraladığı satır başına A2, B1, A1 zincirini korur
Adı genişletmek grafiği tek bir dev güçlü bağlantılı bileşene çevirir; aynı formülleri örtük kesişimle değerlendirmek ise onu, zaman çizelgesi satırı başına kısa zincirlere dönüştürür
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;
    // Skaler konum: Vertical, formül 1. satırda olduğundan A1 daralır
    Sheet.Cells[1, 2].Formula := '=IF(TRUE,Vertical+1,0)';
    Sheet.Cells[2, 1].Formula := '=B1+1';
    // Tanımı başka bir ad olan bir ad yine kesişir; dolayısıyla bu A2
    Sheet.Cells[2, 2].Formula := '=Alias';
    // Referans sınıflı argüman: bütün alan toplanır, kesişim yok
    Sheet.Cells[3, 2].Formula := '=SUM(Vertical)';
    // 6. satır A1:A2 dışındadır; kesişim boştur ve IFERROR yakalar
    Sheet.Cells[6, 2].Formula := '=IFERROR(Vertical,42)';

    if Book.Recalculate = lxOk then
    begin
      // B1 = 2, A2 = 3, B2 = 3, B3 = 4, B6 = 42
      // Vertical burada A1:A2 genişletilse B1 -> A2 -> B1 yanlış bir döngüye döner
    end;
  finally
    Book.Free;
  end;
end;

HotXLS bir argümanın skaler olduğuna nasıl karar veriyor?

HotXLS cevabı argümanın biçiminden değil işlev tablosundan okur. TXLSFormula.InitFuncHash'deki her girdi, isteğe bağlı bir argüman başına sınıf dizgesiyle THashFunc.SetValue üzerinden kaydedilir: 'IF' '100' taşır, 'SUMIF' '010', 'VLOOKUP' '1011' ve 'SUM' hiçbirini taşımaz; dolayısıyla tüm argümanları işlev düzeyi 0 sınıfına düşer. TXLSFormula.FunctionArgumentClass(APtg, AArgument) o baytı THashFuncEntry.ArgClass üzerinden açar ve 1 sonucu değer sınıfı demektir. Bunlar, [MS-XLS] §2.2.2'nin işlenen simgelerine atadığı üç sınıfın aynısıdır ve kodlayıcı da onlara yaslanır: bir referans yazarken ptg'yi $24 + $20 * aClass olarak hesaplar; bu da 0 sınıfı için PtgRef, 1 için PtgRefV ve 2 için PtgRefA üretir. Excel'in yazdığı bir BIFF dosyası o sınıfı her referans simgesinde saklar; dolayısıyla tablosu spesifikasyonla uyuşan bir motor, "bu argüman skaler mi" sorusuna veriye bakmadan yanıt verir. SUMIF'in orta argümanı ölçüttür; bir değerdir. Birinci ile üçüncü alanlardır; referanslardır. SUMPRODUCT, işlev düzeyi 2 sınıfıyla — dizi — kayıtlıdır; =SUMPRODUCT(Vertical,Vertical)'un hâlâ bütün alanı çarpmasının nedeni budur

Üç fonksiyon ilk argümandan sonrası için kendi tablo girdisine hiç bakmaz. IF (ptg 1), CHOOSE (ptg 100) ve IFERROR (ptg 255) seçtikleri şeyi olduğu gibi geçirir, yani dal argümanları fonksiyonun kendi bulunduğu konumun sınıfını miras alır. G2'deki =CHOOSE(1,Vertical,0) ifadesinin A2'ye çözümlenmesini, hemen yanındaki =SUMIF(Vertical,">0",Vertical) ifadesinin ise hâlâ iki satırı toplamasını sağlayan tek kural bu; bir amortisman planının en çok zorladığı kural da bu, çünkü dönem hücreleri kredinin hâlâ açık olup olmadığını test etmek için IF fonksiyonuna yaslanır

HotXLS'in örtük kesişim için argüman sınıflarını nereden okuduğu: IF 100, SUMIF 010, VLOOKUP 1011 kaydeder, SUM hiçbir şey kaydetmez ve argümanları sınıf 0'a düşer; encoder referans simgelerini ptg $24 artı $20 çarpı sınıf olarak yazarak PtgRef, PtgRefV ve PtgRefA üretir; geçiren işlevler IF, CHOOSE ve IFERROR ise bulundukları konumun sınıfını miras alır
Sınıf tablosu spesifikasyonla uyuştuğu için motor, bir argümanın skaler olup olmadığını veriye bakmadan yanıtlayabilir; iki satırı da toplayan bir SUMIF'in yanında A2'ye çözülen CHOOSE tek bir kuraldan gelir

Sınıfı bağımlılık yürüyüşü boyunca taşımak

lxCalc.pas'daki bağımlılık çıkarıcı, derlenmiş söz dizimi ağacı üzerinde özyinelemeli bir Walk'tur ve iki kez vardır: çalışma kitabı başına grafik için TXLSCalculator.ExtractDependencies'te ve sayfalar arası grafik için ExtractWorkspaceDependencies'te. Her iki yürüyücü de iki ek parametre taşır. AScalar, bir formülün kökünde True başlar, her işlev çocuğu için FunctionArgumentClass'tan yeniden hesaplanır ve ptg 1, 100 ve 255'in dal argümanları için değişmeden geçirilir. ANameRoot, yürüyücü bir adın derlenmiş tanımına indiğinde yalnızca o anda True olur ve yalnızca SA_GROUP düğümleriyle — parantezlerle — hayatta kalır; böylece =A1:A2+1 olarak tanımlı bir ad, düz bir alanla karıştırılmaz. Her iki bayrak da bir SA_RANGE düğümünde True iken AddResolvedRange, bağımlılığı kaydetmeden önce alanı değerlendiricinin kullandığı yardımcıyla daraltır. Yardımcı, baştan sona alıntılanmaya değecek kadar kısadır

HotXLS'te ad bağımlılıklarını koruyan IntersectNamedScalarRange kararı: zaten tek hücre olan bir aralık olduğu gibi geçer; tek sütun, CurRow içinde kalıyorsa formül satırına daralır; tek satır, formül sütununa daralır; geri kalan her şey, iki boyutlu bir alan ya da aralık dışı bir satır, değerlendirmede #VALUE! üretir ve hiç bağımlılık kaydetmez
Hem bağımlılık yürüyücüleri hem değerlendirici aynı yardımcıyı çağırır; böylece bir formülün okuduğu değer ile grafiğin kaydettiği kenar, kesiştirilmiş bir ad hakkında asla anlaşamaz
function IntersectNamedScalarRange(CurRow, CurCol: Integer;
  var Row1, Row2, Col1, Col2: Integer): Boolean;
begin
  Result := False;
  if (Row1 = Row2) and (Col1 = Col2) then Exit(True);   // zaten bir hücre
  if (Col1 = Col2) and (CurRow >= Row1) and (CurRow <= Row2) then
  begin
    Row1 := CurRow; Row2 := CurRow;                     // tek sütun: bu satırı al
    Exit(True);
  end;
  if (Row1 = Row2) and (CurCol >= Col1) and (CurCol <= Col2) then
  begin
    Col1 := CurCol; Col2 := CurCol;                     // tek satır: bu sütunu al
    Result := True;
  end;
end;

Yardımcının reddettiği her şey — iki boyutlu bir alan, çok sayfalı bir referans ya da satırı adlandırılmış sütunun dışında kalan bir formül — değerlendirme tarafında #VALUE! ve grafik tarafında hiçbir bağımlılık üretir; Excel'in boş bir kesişim için yaptığı da budur. Değerlendirme tarafı TXLSCalculator.GetValueItemName'dedir: derlenmiş tanımdan SA_GROUP sarmalarını soyar ve kök bir SA_RANGE ise GetRangeInfo çağırır, kesiştirir ve tek hücreyi bütün tanımı değerlendirmek yerine FGetValue ile çeker. Dış referanslar sıradan yolu izler; çünkü kesiştirilecek yerel bir satır yoktur. Bir adın saklamasıyla kapsamının baştan nereden geldiği tanımlı adlar ve sayfalar arası formüller yazısında ele alınır; buradaki nokta yalnızca ad çözüldükten sonra motorun ne yaptığı

Neden MATCH yarım hesaplanmış bir sütunu beklemeli?

Çünkü MATCH'in lookup dizisi argümanı bir tarama referansıdır ve tarama referansları, bilinçli olarak kesin değerlendirme sırasının dışında tutulur. Lookup taraması yazısı, TXLSDepRange.LookupScan'i tanıtır ve tarama kenarlarını sıralamadan dışlamanın neye mal olduğunu açıklar: bir lookup formülü, aralığındaki her hücre yeniden hesaplanmadan önce koşabilir ve bayat değerleri okuyabilir. Etkileşimli bir oturumda bu bir sonraki turda yakınsar. Önbellekleri bayat bir şablonun tek toplu yeniden hesaplamasında yakınsamaz: PaymentCount, =MATCH(0.01,Balances,-1)+1 olarak tanımlıdır; hâlâ bakiye sütununda oturan bayat değerleri okur ve doğru olamayacak bir dönem sayısı döndürür

TXLSDepGraph.TopoOrder bu yüzden tarama kenarlarını yumuşak sıralama kenarı sayar. Kesin giriş derecesinin yanında bir ScanInDeg dizisi tutar; düğüm başına bayat tarama öncülerini sayar ve o öncüler yayıldıkça düşürür; bunu, grafiğin hâlihazırda sakladığı ScanPrecedents, ScanDependents ve ScanPrecedentCount listeleriyle yapar. Her yinelemede Kahn kuyruğu, ScanInDeg'i sıfır olan ilk düğüm için hazır penceresini tarar ve onu başa çeker; bütün hazır düğümler hâlâ bir tarama öncüsünü bekliyorsa baş, kararlı sırasında düşürülür. Tarama kenarları kesin giriş derecesine hiç girmez; dolayısıyla kendi sütunu üzerinde kendine başvuran bir VLOOKUP hâlâ yasaldır ama bitirebilir bir öncüyü bekleyebilen bir lookup bekler. Üç bakiye hücresi bayat değerler taşırken PaymentCount 3 olarak döner; girdiyi sıfıra çevirin ve =IFERROR(PaymentCount,99), #N/A'yı görür ve 99 döndürür

İç içe aritmetik neden Double'a zorlanır

Çünkü Delphi Variant aritmetiğinin tuzağı yalnızca iç içe konumlarda görünür. Üst düzey bir + ya da -'yi iki Double yereline kopyalamak, =B1-A1'i kesin tutar. =IF(TRUE,B1-A1,0) içinde bariz uygulama, aynı çıkarmayı iki Variant üzerinde Value := Value - SubValue olarak koşar ve bir işlenen Int64 hücre değeri, öteki Double olduğunda gözlemlediğimiz sonuç bir Currency oldu; dört ondalık basamaklı sabit noktalı bir tür; dolayısıyla 1066.1854641400994'ten 120 çıkarma, dört basamağa kırpılarak döndü. Her ödemenin önceki satırdan bileşik kurulduğu bir zaman çizelgesinde o hata, toplamlara ulaşmadan önce yüzlerce dönem boyunca yürür; TXLSCalculator.GetValueItem'in işleç koşmadan önce iki işleneni de dönüştürmesinin nedeni budur

// TXLSCalculator.GetValueItem, ikili aritmetik dalı (lxCalc.pas)
if VarIsNull(Value) then Value := 0;
if VarIsNull(SubValue) then SubValue := 0;
// Karışık Int64/Double Variant aritmetiği Currency tipine yükselebilir.
// Tablo aritmetiği kayan nokta hassasiyetini korumak zorundadır.
if VarIsNumeric(Value) then Value := Double(Value);
if VarIsNumeric(SubValue) then SubValue := Double(SubValue);

Koruma, SA_ADD, SA_SUB, SA_MUL ve SA_DIV'in hepsinden önce koşar; dolayısıyla A1'de Int64(120) ve B1'de 1066.1854641400994 varken iç içe fark, toplam, çarpım ve bölümün hepsi çift duyarlıklı kalır. HotXLS, RTL'nin karışık Variant türlerine derleyici sürümleri boyunca uyguladığı her yükseltme kuralını bildiğini iddia etmez; elektronik tablo aritmetiğini IEEE double sayar ve işleci görmesinden önce iki işleneni de double yapar; soruyu ortadan kaldıran da bu

Motor, adlandırılmış sütunlar için neyi garanti eder

Üç mekanizma yerindeyken her iki motor mimarisi de amortizasyon şablonunu bayat önbelleklerden lxOk'a yeniden hesaplar ve 4805 değer, bağımsız bir satır satır beklentisiyle 1E-7 içinde eşleşir. Oraya varmak için ne yineleme etkinleştirilir ne bir hata kodu bastırılır. Bir ad üzerinden gerçek bir döngü — A1'deki =B1, B1 hâlâ Vertical okurken — hâlâ hata döndürür; olması gerektiği gibi

Kurallar Excel'i izler. Örtük kesişim, derlenmiş tanımı parantezler soyulduktan sonra tek sayfalık tek sütun ya da tek satırlık bir alan olan bir ada uygulanır; skaler konumdaki iki boyutlu bir ad #VALUE!'dir; Excel'de olduğu gibi. Tablonun bilmediği bir işlev, FunctionArgumentClass'tan 0 sınıfı alır; dolayısıyla ad argümanları tam genişletilir. Yumuşak sıralama bir tercihtir, garanti değil: yalnızca taramadan oluşan bir döngü kararlı sırada değerlenir ve önbellekte ne varsa okur; lookup taraması yazısının bilinçli olarak kabul ettiği takas budur. HotXLS, Excel kurulu olmadan XLS, XLSX, ODS ve CSV okuyan, yeniden hesaplayan ve yazan yerel bir Delphi ve C++Builder elektronik tablo bileşenidir; ad kesişimi, argüman sınıfı tablosu ve yumuşak tarama sıralaması her biçim için geçerlidir, çünkü hesaplama motoru paylaşılır; güncel işlev kapsamı HotXLS Delphi elektronik tablo bileşeni ürün sayfasında listelenir