Artikel Teknis

Implicit Intersection HotXLS untuk Defined Name di Delphi

Defined name yang merujuk seluruh kolom dibaca Excel sebagai satu sel ketika muncul di posisi skalar: =Vertical+1 di baris 7 berarti "sel baris 7 dari Vertical", bukan seluruh areanya. HotXLS Delphi Component menerapkan implicit intersection itu di v2.382.4 pada dua level, saat evaluasi dan saat ekstraksi dependency, karena template kredit dengan 4805 formula menunjukkan bahwa mendapatkan nilainya dengan benar saja tidak cukup. Ketika dependency walker memperluas name itu ke seluruh areanya, formula hilir yang mengisi sel mana pun di area tersebut menutup siklus yang sebenarnya tidak ada, dan TXLSXWorkbook.Recalculate menolak seluruh workbook

Template yang dimaksud adalah workbook amortisasi kredit standar. Dengan setiap nilai cache diracuni menjadi 777 dan Recalculate dijalankan penuh, kedua arsitektur engine mengembalikan 23, yaitu lxErrorRef, kode circular reference. 3842 dari 4805 formula tidak cocok dengan ekspektasi independen, B18 berisi #VALUE!, E18 masih 777, dan payment count di J7 sempat membaca placeholder di kolom saldo yang belum selesai. Tiga cacat terpisah bersembunyi di balik satu return code, dan artikel ini membahasnya satu per satu beserta kode yang memperbaikinya

Kenapa referensi skalar ke nama kolom menciptakan siklus palsu?

Karena graph dependency hanya mengenal edge, dan edge dari sebuah formula ke area 480 baris berarti 480 edge, salah satunya menunjuk balik lewat sel yang bergantung pada formula itu. Ambil =IF(TRUE,Vertical+1,0) di B1 dengan Vertical didefinisikan sebagai Inputs!$A$1:$A$2, dan =B1+1 di A2. Excel mengevaluasi B1 sebagai A1+1 dan A2 sebagai B1+1, rantai lurus. Walker yang mencatat B1 bergantung pada A1:A2 membuat A2 menjadi preseden B1, padahal A2 sudah mencantumkan B1 sebagai preseden, dan antrian Kahn yang menggerakkan recalculasi inkremental di HotXLS tidak akan pernah melihat salah satu node mencapai in-degree nol. Inilah pola yang membentuk template kredit: setiap baris periode mereferensikan kolom bernama untuk saldo, suku bunga, dan payment count, setiap name mencakup seluruh jadwal, dan setiap baris juga menulis ke kolom-kolom itu. Perluas name-nya dan graph-nya menjadi satu strongly connected component raksasa. Evaluasi dengan implicit intersection dan graph-nya menjadi kumpulan rantai pendek, satu per baris, yang persis dijelaskan ECMA-376 Part 1 §18.17.2 untuk operand referensi yang dikonsumsi di tempat yang menuntut satu nilai

Kenapa nama kolom menutup siklus palsu di HotXLS: dengan Vertical didefinisikan sebagai Inputs!$A$1:$A$2 walker mencatat B1 bergantung pada A1:A2 sementara A2 sudah mencantumkan B1 sebagai preseden, sehingga antrian Kahn tidak pernah terkuras, sedangkan intersection mempersempit B1 ke sel barisnya A1 dan mempertahankan rantai per baris A2, B1, A1 yang diurutkan Recalculate
Memperluas name membuat graph-nya menjadi satu strongly connected component raksasa, dan mengevaluasi formula yang sama dengan implicit intersection mengubahnya menjadi rantai-rantai pendek, satu per baris jadwal
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;
    // Posisi skalar: Vertical menyusut ke A1 karena formulanya ada di baris 1
    Sheet.Cells[1, 2].Formula := '=IF(TRUE,Vertical+1,0)';
    Sheet.Cells[2, 1].Formula := '=B1+1';
    // Name yang definisinya name lain tetap ber-intersection, jadi ini A2
    Sheet.Cells[2, 2].Formula := '=Alias';
    // Argumen kelas referensi: seluruh area dijumlahkan, tanpa intersection
    Sheet.Cells[3, 2].Formula := '=SUM(Vertical)';
    // Baris 6 di luar A1:A2, intersection-nya kosong dan IFERROR menangkapnya
    Sheet.Cells[6, 2].Formula := '=IFERROR(Vertical,42)';

    if Book.Recalculate = lxOk then
    begin
      // B1 = 2, A2 = 3, B2 = 3, B3 = 4, B6 = 42
      // Sebelum v2.382.4 cabang ini tidak terjangkau: B1 -> A2 -> B1 adalah siklus
    end;
  finally
    Book.Free;
  end;
end;

Bagaimana HotXLS memutuskan bahwa sebuah argumen bersifat skalar?

HotXLS membaca jawabannya dari tabel fungsi, bukan dari bentuk argumennya. Setiap entri di TXLSFormula.InitFuncHash didaftarkan lewat THashFunc.SetValue dengan string kelas per-argumen opsional: 'IF' membawa '100', 'SUMIF' membawa '010', 'VLOOKUP' membawa '1011', dan 'SUM' tidak membawa apa pun, sehingga semua argumennya jatuh ke kelas level fungsi 0. TXLSFormula.FunctionArgumentClass(APtg, AArgument) yang baru memaparkan byte itu lewat THashFuncEntry.ArgClass, dan hasil 1 berarti kelas nilai. Ketiganya adalah kelas yang sama yang diberikan [MS-XLS] §2.2.2 ke operand token, dan encoder-nya sudah bergantung pada kelas itu: ketika menulis referensi ia menghitung ptg sebagai $24 + $20 * aClass, yang menghasilkan PtgRef untuk kelas 0, PtgRefV untuk kelas 1, dan PtgRefA untuk kelas 2. File BIFF yang ditulis Excel menyimpan kelas itu di setiap token referensi, jadi engine yang tabelnya cocok dengan spesifikasi bisa menjawab "apakah argumen ini skalar" tanpa melihat datanya. Argumen tengah SUMIF adalah kriterianya, sebuah nilai; argumen pertama dan ketiga adalah area, referensi. SUMPRODUCT didaftarkan dengan kelas level fungsi 2, array, itulah sebabnya =SUMPRODUCT(Vertical,Vertical) tetap mengalikan seluruh area

Tiga fungsi tidak mengonsultasikan entri tabelnya sendiri untuk apa pun setelah argumen pertama. IF (ptg 1), CHOOSE (ptg 100), dan IFERROR (ptg 255) meneruskan apa pun yang mereka pilih, jadi argumen cabangnya mewarisi kelas dari posisi yang ditempati fungsi itu sendiri. Satu aturan itulah yang membuat =CHOOSE(1,Vertical,0) di G2 mengarah ke A2 sementara =SUMIF(Vertical,">0",Vertical) di sebelahnya tetap menjumlahkan kedua baris, dan aturan itulah yang paling sering diuji jadwal amortisasi, karena sel periodenya bersandar pada IF untuk menguji apakah kreditnya masih berjalan

Dari mana HotXLS membaca kelas argumen untuk implicit intersection: IF mendaftarkan 100, SUMIF 010, VLOOKUP 1011 dan SUM tidak sama sekali sehingga argumennya jatuh ke kelas 0, encoder menulis token referensi sebagai ptg $24 plus $20 kali kelasnya sehingga menghasilkan PtgRef, PtgRefV dan PtgRefA, dan fungsi pass-through IF, CHOOSE serta IFERROR mewarisi kelas dari posisi yang ditempatinya
Karena tabel kelasnya cocok dengan spesifikasi, engine bisa menjawab apakah sebuah argumen skalar tanpa melihat datanya, dan CHOOSE yang mengarah ke A2 di sebelah SUMIF yang menjumlahkan kedua baris mengikuti satu aturan yang sama

Membawa kelas itu melewati dependency walk

Ekstraktor dependency di lxCalc.pas adalah Walk rekursif atas pohon sintaks yang sudah dikompilasi, dan ia ada dua kali, sekali di TXLSCalculator.ExtractDependencies untuk graph per-workbook dan sekali di ExtractWorkspaceDependencies untuk graph lintas-workbook. v2.382.4 memberi kedua walker itu dua parameter tambahan. AScalar mulai sebagai True di akar sebuah formula, dihitung ulang untuk setiap anak fungsi dari FunctionArgumentClass, dan diteruskan tanpa perubahan untuk argumen cabang ptg 1, 100, dan 255. ANameRoot menjadi True hanya ketika walker turun ke definisi terkompilasi sebuah name, dan ia hanya bertahan lewat node SA_GROUP, yaitu tanda kurung, sehingga name yang didefinisikan sebagai =A1:A2+1 tidak disalahartikan sebagai area biasa. Ketika kedua flag itu True pada node SA_RANGE, AddResolvedRange mempersempit area dengan helper yang sama yang dipakai evaluator sebelum mencatat dependency-nya. Helper-nya cukup pendek untuk dikutip utuh

Keputusan IntersectNamedScalarRange yang menjaga dependency name di HotXLS: range yang sudah berisi satu sel dilewatkan begitu saja, satu kolom dipersempit ke baris formula ketika CurRow berada di dalamnya, satu baris dipersempit ke kolom formula, dan selain itu, area dua dimensi atau baris di luar jangkauan, menghasilkan #VALUE! saat evaluasi dan tidak mencatat dependency sama sekali
Kedua dependency walker dan evaluatornya memanggil helper yang sama, jadi nilai yang dibaca formula dan edge yang dicatat graph tidak mungkin berbeda pendapat soal name yang di-intersect
function IntersectNamedScalarRange(CurRow, CurCol: Integer;
  var Row1, Row2, Col1, Col2: Integer): Boolean;
begin
  Result := False;
  if (Row1 = Row2) and (Col1 = Col2) then Exit(True);   // sudah berupa satu sel
  if (Col1 = Col2) and (CurRow >= Row1) and (CurRow <= Row2) then
  begin
    Row1 := CurRow; Row2 := CurRow;                     // satu kolom: ambil baris ini
    Exit(True);
  end;
  if (Row1 = Row2) and (CurCol >= Col1) and (CurCol <= Col2) then
  begin
    Col1 := CurCol; Col2 := CurCol;                     // satu baris: ambil kolom ini
    Result := True;
  end;
end;

Apa pun yang ditolak helper itu, area dua dimensi, referensi multi-sheet, atau formula yang barisnya di luar kolom bernama, menghasilkan #VALUE! di sisi evaluasi dan tidak ada dependency sama sekali di sisi graph, yang persis dilakukan Excel untuk intersection kosong. Sisi evaluasinya ada di TXLSCalculator.GetValueItemName: ia melepas pembungkus SA_GROUP dari definisi terkompilasi, dan jika akarnya SA_RANGE ia memanggil GetRangeInfo, melakukan intersect, lalu mengambil satu sel itu lewat FGetValue alih-alih mengevaluasi seluruh definisinya. Referensi eksternal tetap di jalur lama, karena tidak ada baris lokal untuk di-intersect. Dari mana penyimpanan dan scope sebuah name berasal dibahas di artikel defined names dan formula lintas-sheet; poin di sini hanya apa yang dilakukan engine setelah name-nya teresolusi

Kenapa MATCH atas kolom yang baru separuh dihitung membaca 777?

Karena argumen lookup-array di MATCH adalah scan reference, dan scan reference sengaja dikecualikan dari urutan evaluasi. Artikel lookup scan memperkenalkan TXLSDepRange.LookupScan dan ditutup dengan bagian berjudul "Apa yang Anda korbankan dengan mengecualikan scan edge dari pengurutan": formula lookup bisa berjalan sebelum setiap sel di range-nya dihitung ulang dan membaca nilai basi. Di sesi interaktif itu konvergen pada pass berikutnya. Di recalculasi batch atas template yang diracuni, tidak, dan PaymentCount, yang didefinisikan sebagai =MATCH(0.01,Balances,-1)+1, membaca placeholder 777 yang masih bercokol di kolom saldo lalu mengembalikan jumlah periode yang mustahil benar

TXLSDepGraph.TopoOrder kini memperlakukan scan edge sebagai soft ordering edge. Di samping in-degree keras, ia menyimpan array ScanInDeg, menghitung preseden scan yang kotor per node dan menguranginya saat preseden itu dikeluarkan, memakai daftar ScanPrecedents, ScanDependents, dan ScanPrecedentCount yang sudah disimpan oleh perubahan sebelumnya. Di setiap iterasi antrian Kahn memindai jendela siapnya untuk node pertama yang ScanInDeg-nya nol lalu menukarnya ke kepala; jika setiap node yang siap masih menunggu preseden scan, kepalanya dikeluarkan dalam urutan stabilnya. Scan edge tidak pernah masuk ke in-degree keras, jadi VLOOKUP yang mereferensikan kolomnya sendiri tetap legal, tapi lookup yang bisa menunggu preseden yang bisa diselesaikan kini benar-benar menunggu. Regresi yang mengunci ini, LookupScan_WaitsForDirtyFormulaValues, meracuni tiga sel saldo menjadi 777 dan mengharapkan PaymentCount kembali sebagai 3, lalu membalik inputnya ke nol dan mengharapkan =IFERROR(PaymentCount,99) melihat #N/A dan mengembalikan 99

Dari mana asal pemotongan empat desimal itu?

Dari aritmetika Variant Delphi, dan hanya di posisi bersarang. Operator biner di TXLSCalculator.GetValueItem sudah menyalin + atau - di level teratas ke dua variabel lokal Double, jadi =B1-A1 baik-baik saja. Di dalam =IF(TRUE,B1-A1,0) pengurangan yang sama berjalan sebagai Value := Value - SubValue atas dua Variant, dan ketika satu operand adalah nilai sel Int64 dan yang lain Double, hasil yang kami amati adalah Currency, tipe fixed-point dengan empat tempat desimal, sehingga 1066.1854641400994 dikurangi 120 kembali terpotong ke empat desimal. Di jadwal yang setiap pembayarannya dimajemukkan dari baris sebelumnya, galat itu berjalan melewati ratusan periode sebelum mencapai totalnya

// TXLSCalculator.GetValueItem, cabang aritmetika biner (lxCalc.pas)
if VarIsNull(Value) then Value := 0;
if VarIsNull(SubValue) then SubValue := 0;
// Aritmetika Variant campuran Int64/Double bisa terpromosi ke Currency.
// Aritmetika spreadsheet harus mempertahankan presisi floating-point.
if VarIsNumeric(Value) then Value := Double(Value);
if VarIsNumeric(SubValue) then SubValue := Double(SubValue);

Guard-nya berjalan sebelum SA_ADD, SA_SUB, SA_MUL, maupun SA_DIV, dan regresi Arithmetic_MixedInt64AndDoubleKeepsPrecision menyimpan Int64(120) di A1 dan 1066.1854641400994 di B1, lalu memeriksa selisih dan jumlah bersarangnya sampai 1E-10 serta hasil kali dan baginya sampai 1E-8 dan 1E-12. HotXLS tidak mengklaim tahu setiap aturan promosi yang diterapkan RTL pada tipe Variant campuran di berbagai versi compiler; ia mengklaim bahwa aritmetika spreadsheet adalah IEEE double, dan sekarang ia membuat kedua operandnya double sebelum operator melihatnya, yang menghapus pertanyaannya

Apa yang dijamin perbaikan ini, dan apa yang tidak

Setelah v2.382.4 kedua arsitektur engine mengembalikan lxOk untuk template yang diracuni, semua 4805 nilai cache cocok dengan ekspektasi baris-per-baris independen dalam toleransi 1E-7, dan assertion bahwa cache-nya benar-benar diracuni, bahwa hash sumbernya tidak berubah, dan bahwa setiap formula masih ada semuanya terpenuhi. Tidak ada iterasi yang diaktifkan dan tidak ada kode galat yang ditutup untuk mencapai itu. Siklus sungguhan lewat sebuah name, =B1 di A1 dengan B1 masih membaca Vertical, tetap mengembalikan galat, dan uji NamedScalarRanges_IntersectWithoutFalseCycles ditutup dengan menegaskan persis itu

Batasnya layak dinyatakan terang-terangan. Implicit intersection hanya berlaku untuk name yang definisi terkompilasinya, setelah tanda kurungnya dilepas, berupa area satu kolom atau satu baris di satu sheet; name dua dimensi di posisi skalar menghasilkan #VALUE!, sama seperti di Excel, dan fungsi yang tidak dikenal tabel mendapat kelas 0 dari FunctionArgumentClass, sehingga argumen name-nya tetap diperluas penuh. Soft ordering adalah preferensi, bukan jaminan: siklus yang hanya berupa scan tetap dievaluasi dalam urutan stabil dan membaca apa pun yang ada di cache, dan itulah perilaku yang diterima artikel lookup scan secara sadar. Dan hasil seluruh template diverifikasi terhadap skrip ekspektasi independen, bukan terhadap engine spreadsheet lain, karena office suite acuan tidak menyelesaikan recalculasi template aslinya dalam anggaran 60 detik. HotXLS adalah komponen spreadsheet Delphi dan C++Builder native yang membaca, menghitung ulang, dan menulis XLS, XLSX, ODS, serta CSV tanpa Excel terpasang; intersection name, tabel kelas argumen, dan soft scan ordering berlaku untuk semua format karena engine perhitungannya dipakai bersama, dan cakupan fungsi terkini tercantum di halaman produk HotXLS Delphi spreadsheet component