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
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
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
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