Teknik Makale

HotXLS'te AGGREGATE Seçenek Matrisi ve Kapı Sızıntısı

Delphi ve C++Builder için yerel Excel elektronik tablo bileşeni HotXLS, Eylül 2026'da birbiriyle ilişkili iki AGGREGATE düzeltmesi yayımladı. Sürüm 2.382.0 seçenekler bağımsız değişkenini düzeltti: 1/3/5/7 kodları gizli satırları, 2/3/6/7 kodları hataları ve 0 ile 3 arası iç içe SUBTOTAL ve AGGREGATE hücrelerini Microsoft'un belgelediği biçimde yok sayar. Sürüm 2.382.3 ise bu seçim bayraklarının, fonksiyonun başvurduğu hücrelerin değerlendirilmesine sızmasını engelledi. Birinci kusur, tablo aktarma hatalarının her zaman olduğu gibi utanç vericidir: bit konumları yer değiştirmişti, dolayısıyla sıfırdan farklı bir seçenek kodu kullanan her formül, yazarının istemediği bir politikaya kavuşuyordu. İkincisi daha ilginçtir, çünkü bağlamı özyinelemeli bir yürüyüşe taşımak için geçici bir alan kullanan her değerlendiricinin karşılaşacağı bir biçimdir. Dıştaki bir toplama bir bayrağı kurar, bir aralığı yürür ve formülü henüz hesaplanmamış bir hücreye ulaşır. O formül aynı hesaplayıcı üzerinde çalışır, aynı kurulu bayrağı görür ve sessizce yanlış satırları toplar; ortaya çıkan sayı, formül metninden kimsenin açıklayamayacağı kadar kaymıştır

AGGREGATE seçenekleri 0 ile 7 tam olarak neyi seçer?

AGGREGATE'in seçenekler bağımsız değişkeni üç bitlik bir matristir ve üç bit bağımsızdır. Bit 0 (değer 1) gizli satırları yok saymak, bit 1 (değer 2) hata değerlerini yok saymak, bit 2 (değer 4) ise iç içe SUBTOTAL ve AGGREGATE hücrelerini yok saymayı durdurmak anlamına gelir, çünkü düşük kodlarda onları atlamak varsayılandır. Bununla ilgili iki şeyi ters anlamak kolaydır. Gizli satır biti ortadaki değil alt bittir, dolayısıyla AGGREGATE(9,1,...) filtrelenmiş toplam biçimidir ve AGGREGATE(9,2,...) hataya toleranslı olandır. İç içe toplama politikası ise diğer ikisine göre terstir: yalnızca 4 ile 7 arasındaki kodlar, kendi formülü SUBTOTAL ya da AGGREGATE olan bir hücreyi sıradan bir değer sayar. ECMA-376 Bölüm 1 §18.17.7 SUBTOTAL'ı 1-11 ve 101-111 kodları arasında aynı gizli satırı dahil et ya da hariç tut ayrımıyla tanımlar ve OOXML dosyalarında _xlfn. öneki altında saklanan AGGREGATE, bu ayrımı seçenekler bağımsız değişkenine genelleştirir; dolayısıyla Microsoft'un AGGREGATE fonksiyonu için yayımladığı tablo, bir kolaylık değil bir motorun karşılaması gereken sözleşmedir

SeçenekGizli satırlarHata değerleriİç içe SUBTOTAL / AGGREGATE
0dahilyayılıryok sayılır
1yok sayılıryayılıryok sayılır
2dahilyok sayılıryok sayılır
3yok sayılıryok sayılıryok sayılır
4dahilyayılırdahil
5yok sayılıryayılırdahil
6dahilyok sayılırdahil
7yok sayılıryok sayılırdahil

HotXLS'te AGGREGATE seçenekleri neden tersti?

Çünkü özgün TXLSCalculator.CalcAggregateFunc tablodan değil tablonun bir özetinden yazılmıştı. ignoreErrors := (optCode >= 4) and (optCode <= 7) hesaplıyor ve gizli satır kapısını 2, 3, 6 ve 7 kodları için kuruyordu; iç içe toplama politikası ise hiç uygulanmamıştı. SUBTOTAL ve AGGREGATE gizli satırlar üzerine önceki yazı bu boşluğu açık bir sınır olarak listeliyor ve eski eşlemeyi o zamanki hâliyle anlatıyordu; tarif kod hakkında doğru, Excel hakkında yanlıştı ve çoğu insanın birleştirdiği iki politika, yani gizli artı hatalar, her iki tabloda da 3 ve 7 kodlarına düştüğü için bunu uzun süre kimse fark etmedi. Yer değiştirmeyi yalnızca tek bitlik bir kod açığa çıkardı: AGGREGATE(9,1,A1:A4) filtrelenmemiş toplamı döndürüyor ve AGGREGATE(9,2,...) gizli satırları atlarken #DIV/0! hatasını yine yayıyordu. Kusur, lxCalc.pas dosyasının statik incelemesinden çıktı ve projenin bilinen sorunlar kaydına HXLS-008 olarak işlendi; bir müşteri dosyasından çıkmadı, ki bu da tek bitlik kodların üretim çalışma kitaplarında ne kadar seyrek göründüğü hakkında bir şey söyler. Sürüm 2.382.0 çözümlemeyi üç küme üyelik testi olarak yeniden yazdı ve iç içe politika için, çalışma kitabının TXLSIsRowHidden yanında sağladığı yeni bir TXLSIsSubtotalCell geri çağrısı üzerinden bağlanan ikinci bir kapı ekledi

v2.382.0 öncesi ve sonrası HotXLS AGGREGATE seçenek çözümlemesi: özgün CalcAggregateFunc gizli satır kapısını 2, 3, 6, 7 kodları için kuruyor ve hataları 4'ten yukarısında yok sayıyordu, iç içe politika ise yoktu; düzeltilmiş çözümleme gizli satırları 1, 3, 5, 7'de, hataları 2, 3, 6, 7'de ve iç içe atlamayı 0 ile 3 arasında sınar
Yer değiştirmeyi yalnızca tek bitlik kodlar açığa çıkardı, çünkü popüler gizli artı hatalar bileşimi her iki tabloda da 3 ve 7 kodlarına düşer; 0 ile 7 dışındaki kodlar artık Excel'in onları reddettiği gibi tam olarak lxErrorValue döndürür
// TXLSCalculator.CalcAggregateFunc, v2.382.3 biçimi
if (optCode < 0) or (optCode > 7) then
begin
  Result := lxErrorValue;            // Excel 0..7 dışındaki kodları reddeder
  Exit;
end;
ignoreErrors := optCode in [2, 3, 6, 7];
prevIgnoreHidden := FIgnoreHiddenRows;
prevIgnoreSubtotal := FIgnoreSubtotalCells;
FIgnoreHiddenRows := (optCode in [1, 3, 5, 7]) and Assigned(FIsRowHidden);
FIgnoreSubtotalCells := (optCode in [0, 1, 2, 3]) and Assigned(FIsSubtotalCell);
try
  // ... function_num'ı iç iftab'a eşle, ref1..refN aralığını yürü ...
finally
  FIgnoreHiddenRows := prevIgnoreHidden;
  FIgnoreSubtotalCells := prevIgnoreSubtotal;
end;

İki bayrağın, yalnızca seçenek istediğinde set edilmek yerine koşulsuz atandığına dikkat edin. v2.382.0 sürümü hâlâ if ... then FIgnoreHiddenRows := True kullanıyordu; bu, bir SUBTOTAL(109, ...) içine yerleştirilmiş 4 kodlu bir AGGREGATE'in gizli satır kapısını temizlemek yerine dıştaki kapıyı devralması anlamına geliyordu. Çözülen değeri girişte atamak ve önceki değeri finally bloğunda geri yüklemek, her AGGREGATE çağrısının kendi politikasına yalnızca kendi yürüyüşü süresince sahip olmasını sağlar. Sürüm 2.382.0 ayrıca dizi biçimini dürüst hâle getirdi: bir bağımsız değişken bir ya da iki boyutlu bir Variant dizisine çözüldüğünde CalcAggregateFunc artık her elemanı yürür ve hata politikasını eleman başına uygular; eski kod ise yalnızca NaN bir double olup olmadığını sınar, aksi hâlde dizinin tamamını ExcelSum fonksiyonuna verirdi

Dıştaki bir AGGREGATE neden başvurduğu formüllere sızar?

Çünkü FIgnoreHiddenRows ile FIgnoreSubtotalCells hesaplayıcının alanlarıdır ve hesaplayıcı, bir yeniden hesaplama sırasında değerlendirilen her formül tarafından paylaşılır. Kapılar tam olarak altı hücre yürüyüş döngüsünün her imzaya parametre geçirmeden onlara bakabilmesi için geçici alan olarak tasarlanmıştı ve bu tasarım, bir kapı kuruluyken çalışan her şeyin o kapıyı kuran toplamaya ait olması koşuluyla sağlamdır. Varsayım tek bir noktada kırılır: FGetValue. Bir yürüyücü çalışma kitabından bir hücre değeri istediğinde ve o hücre önbelleklenmiş sonucu olmayan bir formül taşıdığında, çalışma kitabı formülü derleyip aynı TXLSCalculator üzerinde, dıştaki kapılar hâlâ kuruluyken anında değerlendirir. HotXLS.WorkbookApiTests.pas içindeki regresyon fikstürü arızayı dört hücreyle gösterir. A1 10 tutar, A2 gizli bir satırda 20 tutar, A3 =1/0 tutar ve A4, doğru değeri 30 olan =SUBTOTAL(9,A1:A2) tutar. Şimdi =AGGREGATE(9,7,A1:A4) değerlendirin: gizli satırları yok say, hataları yok say, iç içe toplamayı değer say. Excel 10 + 30 = 40 döndürür. A4 önbelleklenmemişken 2.382.3 öncesi motor gizli satır kapısını kurdu, A4'e yürüdü, değerlendirmesini tetikledi ve 9 kodlu CalcSubtotalFunc kurulu kapıyı devraldı, çünkü bayrağı yalnızca 101 ile 111 arasındaki kodlar için set ediyor ve hiç temizlemiyor. A4 30 yerine 10 olarak değerlendirildi ve dıştaki toplam 20 olarak geri geldi. Yanlış sayıyı üreten yolda iki formülden hiçbiri gizli satırlardan söz etmez

Dıştaki bir HotXLS AGGREGATE'in öncüllerine nasıl sızdığı: 7 kodu için FIgnoreHiddenRows kuruluyken yürüyüş, A1:A2 üzerinde SUBTOTAL 9 tutan önbelleklenmemiş A4'e ulaşır, FGetValue onu aynı hesaplayıcıda değerlendirir, CalcSubtotalFunc kapıyı devralıp 30 yerine 10 döndürür ve toplam Excel'in 40 döndürdüğü yerde 20 bildirir
İç içe kapı diğer yöne de sızdı ve CalcSubtotalFunc çıkışta FIgnoreSubtotalCells değerini geri yüklemek yerine sıfırlıyordu; böylece yürüyüş ortasında ulaşılan önbelleklenmemiş bir toplamadan sonraki her hücre için dıştaki politika devre dışı kalıyordu

İç içe toplama kapısı diğer yöne de aynı biçimde sızdı. 0 ile 3 arasındaki kodlarda FIgnoreSubtotalCells kurulur ve GetValueItemRange içindeki genel aralık yürüyücüsü ona uyar, böylece formülü =SUM(B1:B3) olan bir öncül, B2 bir SUBTOTAL içeriyorsa B2'yi sessizce düşürürdü. Daha kötüsü, CalcSubtotalFunc çıkışta FIgnoreSubtotalCells değerini önceki değere geri yüklemek yerine False'a sıfırlar, dolayısıyla yürüyüş ortasında ulaşılan önbelleklenmemiş bir SUBTOTAL öncülü, ondan sonraki her hücre için dıştaki kapıyı devre dışı bırakır. Projenin bilinen sorunlar kaydı bunu HXLS-008 altında iç içe seçim durumu sızıntısı olarak dosyalar ve bu, hata sınıfı için doğru addır: onu kuran çerçeve için doğru, onu devralan her çerçeve için yanlış olan genel bir geçici bayrak

AggregateGetCellValue ve AggregateGetItemValue yürüyüşü nasıl yalıtır

v2.382.3'teki düzeltme, AGGREGATE'in kendi hesaplamadığı bir değeri okuduğu her noktanın çevresine bir sınır koyar. TXLSCalculator.AggregateGetCellValue ham FGetValue çağrısını sarar: iki bayrağı da saklar, temizler, getirmeyi yapar ve bir finally bloğunda geri yükler. Dıştaki toplama yine de az önce getirdiği hücreye kendi politikasını uygular, çünkü gizli satır ve iç içe hücre sınamaları getirmenin çevresinde, yürüyücüde yapılır; ama öncül formülün kendisi, Excel'in yaptığı gibi, hiçbir politikayla çalışmaz

HotXLS v2.382.3 yalıtımı: AggregateGetCellValue iki kapı bayrağını da saklar, temizler, FGetValue üzerinden getirir ve bir finally bloğunda geri yükler; böylece bir öncül formül politika olmadan değerlendirilirken dıştaki yürüyücü getirmenin çevresinde gizli satır ve iç içe hücre sınamalarını yine uygular
AggregateGetItemValue hesaplanmış dizi bağımsız değişkenleri için aynısını yapar ve getirme hatalarını VarAsError'a eşler; kaynak sınırı kodu ise hataları yok sayma seçenekleri altında bilinçli olarak asla yok sayılabilir bir hata sayılmaz
function TXLSCalculator.AggregateGetCellValue(SheetIndex, Row, Col: Integer;
  var Value: Variant; var OutOfRange: Boolean): Integer;
var
  Hidden, Nested: Boolean;
begin
  Hidden := FIgnoreHiddenRows;
  Nested := FIgnoreSubtotalCells;
  FIgnoreHiddenRows := False;        // bir öncül formül kendi politikasına sahiptir
  FIgnoreSubtotalCells := False;
  try
    Result := FGetValue(SheetIndex, Row, Col, Value, OutOfRange);
  finally
    FIgnoreHiddenRows := Hidden;
    FIgnoreSubtotalCells := Nested;
  end;
end;

AggregateGetItemValue aralık olmayan bağımsız değişkenler için aynısını yapar ve bayrak temizlemekten fazlasını yapmak zorundadır, çünkü A1:A4/(B1:B4-20) gibi bir bağımsız değişken, eleman biçimi korunması gereken hesaplanmış bir dizidir. Sarmalayıcı düz bir aralığı AggregateGetCellValue üzerinden iki boyutlu bir Variant dizisine dönüştürür, hata kodu döndüren bir hücreyi hata politikası yine eleman başına uygulanabilsin diye VarAsError'a eşler ve ikili ile tekli operatör düğümlerinde (SA_ADD, SA_DIV, SA_UNARMINUS ve geri kalanı) ApplyArrayBinaryOp ile ApplyArrayUnaryOp üzerinden özyinelemeyle iner; başka her şey normal GetValueItem yoluna düşer. Dönüştürmenin önünde iki koruma durur: EffectiveFormulaArrayMemoryLimit değerinden büyük bir aralık lxErrorResourceLimit döndürür ve çok sayfalı ya da ters çevrilmiş bir aralık #VALUE! döndürür. Bir kaynak sınırı kodu, 2/3/6/7 seçenekleri altında bile bilinçli olarak yok sayılabilir bir hücre hatası sayılmaz, çünkü kullanıcı #N/A atlamayı istedi diye kendi bellek yetersizliği sinyalini yutan bir motor yalan söylüyor olurdu. Üç AGGREGATE yürüyücüsünün hepsi, SUM ailesi için AggregateCollectRange, STDEV, VAR ve PRODUCT için AggregateReduceVariance ve MEDIAN ile kantil biçimleri için AggregateReduceWithK, FGetValue ve GetValueItem yerine bu iki sarmalayıcıya geçirildi ve her biri FIsSubtotalCell üzerinden iç içe hücre sınamasını kazandı

AGGREGATE hataları yok saymadığında hangi hatayı döndürür?

v2.382.3'ten bu yana özgün olanı. Sürüm 2.382.0 hata hücrelerini doğru algıladı ama her birini lxErrorValue içine indirdi, dolayısıyla #DIV/0! tutan bir hücre üzerinde AGGREGATE(9,4,A1:A3) #VALUE! döndürüyordu; oysa Excel karşılaştığı ilk hatayı değiştirmeden yayar. Yerine geçen AggregateErrorCode yardımcısı bir Variant'ı, gerçek bir varError olsun ya da yedi hata dizesinden biri olsun, karşılık gelen lxError* koduna eşler ve AggregateValueIsError artık yalnızca sıfırdan farklı sonuç sınamasıdır. Her yürüyücü gördüğü ilk hata kodunu kaydeder ve o kodu döndürür; bu aynı zamanda formülü hiç hesaplanmamış ve hatası önbelleklenmiş bir Variant yerine FGetValue dönüş kodu olarak gelen bir hücrenin de önbelleklenmiş olanla aynı biçimde yayılması anlamına gelir. AggregateCollectRange içinde iki sayma fonksiyonu özel işlem görür ve bu işlem SUM'a değil SUBTOTAL'a uyar. İç fonksiyon 0, yani COUNT için, seçenek kodu ne olursa olsun bir hata hücresi asla sayılmaz ve asla yayılmaz, çünkü COUNT yalnızca sayıları sayar. İç fonksiyon 169, yani COUNTA için, bir hata hücresi boş olmayan bir değerdir ve seçenek kodu hataları yok saymıyorsa 1 sayılır; yok sayıyorsa atlanır. Bu asimetri, Excel'in COUNT ve COUNTA ile AGGREGATE dışında da davrandığı biçimdir ve genel bir “hata varsa yay” kuralının sessizce yanlış yaptığı türden bir ayrıntıdır

Sekiz seçenekli regresyon matrisi neyi doğrular

Yukarıda anlatılan fikstür, AggregateFunc_OptionMatrixCoversHiddenErrorsAndNestedAggregates içinde tam bir matris olarak çalıştırılır: 0 ile 7 arasındaki her seçenek kodu için A1:A4 üzerinde hem SUM hem MEDIAN biçimi değerlendirilir ve sonuç elle türetilmiş bir beklentiyle karşılaştırılır. 0, 1, 4 ve 5 kodları A3'teki #DIV/0! hatasını yaymak zorundadır, çünkü hiçbiri hataları yok saymaz. Kod 2, iç içe A4 atlanarak 10 ile 20'den SUM 30 ve MEDIAN 15 verir. Kod 3, 10 ve 10 verir. Kod 6, A4'teki 30 artık sayıldığı için 60 ve 20 verir. Kod 7, 40 ve 20 verir ki bu, sızıntı düzeltmesinden önce 20 dönen durumdur. Bilinen sorunlar kaydına geçirilen daha geniş kabul koşusu, on dokuz fonksiyon numarasının tamamını sekiz kodun tamamına karşı, her öncül hem önbelleklenmiş hem önbelleklenmemiş olarak kapsar: Win32 ve Win64'te 304 senaryo

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Data');
    Sheet.Cells[1, 1].Value := 10;
    Sheet.Cells[2, 1].Value := 20;
    Sheet.Cells[3, 1].Formula := '=1/0';
    Sheet.Cells[4, 1].Formula := '=SUBTOTAL(9,A1:A2)';   // grup toplamı = 30
    Sheet.RowHidden[2] := True;

    Sheet.Cells[6, 1].Formula := '=AGGREGATE(9,1,A1:A4)'; // #DIV/0!  gizli atlanır, hata yayılır
    Sheet.Cells[7, 1].Formula := '=AGGREGATE(9,3,A1:A4)'; // 10       gizli + hata + iç içe atlanır
    Sheet.Cells[8, 1].Formula := '=AGGREGATE(9,6,A1:A4)'; // 60       yalnızca hatalar atlanır
    Sheet.Cells[9, 1].Formula := '=AGGREGATE(9,7,A1:A4)'; // 40       v2.382.3 öncesi 20 idi
    Book.Recalculate;
    Book.SaveAs('aggregate-options.xlsx');
  finally
    Book.Free;
  end;
end;

Sınırın hâlâ nerede olduğu

Bunun üzerine kurulmadan önce bilinmeye değer üç sınır var. Birincisi, iç içe toplama yüklemi metinseldir. TXLSXWorkbook.GetCalcIsSubtotalCell ve klasik motor ikizi, bir hücrenin formülü baştaki eşittir işareti olsun ya da olmasın SUBTOTAL(, AGGREGATE( ya da _xlfn.AGGREGATE( ile başlıyorsa True yanıtlar, dolayısıyla =IF(C1,SUBTOTAL(9,B1:B9),0) ya da =SUBTOTAL(9,B1:B9)*2 gibi bir formül iç içe sayılmaz ve Excel'in atlayacağı yerde 0 ile 3 arasındaki kodlarca çift sayılır; hesaplanmış toplamalar üreten bir üretici, toplama çağrısını formülün başında tutmalıdır. İkincisi, yalıtım üç AGGREGATE yürüyücüsünde yaşar. CalcSubtotalFunc hâlâ FGetValue fonksiyonunu doğrudan çağıran GetValueItemRange, CollectRangeValues ve SubtotalReduceVariance üzerinden yürür, dolayısıyla aralığında önbelleklenmemiş bir öncül formül bulunan bir SUBTOTAL(109, ...) gizli satır kapısını o öncüle yine geçirebilir. Tam bir Recalculate öncülleri bağımlılardan önce değerlendirir, bu yüzden önbelleklenmiş yol izlenir ve kapı hiç devralınmaz; maruz kalma Calculate üzerinden yapılan anlık değerlendirmelerle ve önbelleklenmiş değerler olmadan yüklenen çalışma kitaplarıyla sınırlıdır ve büyük modelleri çevik tutmak için bağımlılık grafiği üzerinden artımlı yeniden hesaplamaya güveniyorsanız, bu sızıntıyı uykuda tutan şey de aynı sıralama garantisidir. Üçüncüsü, iki kapı da Assigned(FIsRowHidden) ve Assigned(FIsSubtotalCell) koşullarına bağlıdır. Her iki çalışma kitabı cephesi geri çağrıları kurucularında bağlar, ama bir TXLSCalculator örneğini elle, yalnızca iki özgün bağımsız değişkenle kuran kod her seçenek kodu için eski, her şeyi dahil eden davranışı sessizce alır. Bir toplam yanlış görünüp formül metni doğru göründüğünde, bir öncülün devralınmış bir kapı altında mı değerlendirildiğini yoksa bir geri çağrının hiç mi bağlanmadığını görmenin en hızlı yolu değerlendirmeyi adım adım izlemektir

Burada anlatılan hesaplama motoru, seçenek çözücüsü, yalıtılmış getirme sarmalayıcıları ve onları sabitleyen regresyon matrisi, HotXLS Delphi elektronik tablo bileşeni ile birlikte kaynak olarak gelir; bu bileşen Delphi ve C++Builder'da Excel kurulumu olmadan XLS, XLSX ve ODS çalışma kitaplarını okur, yazar ve yeniden hesaplar