Excel 365 แทรก @ ลงในสูตรอย่าง =SUM(A1:B1*{10,100}) แล้วโชว์ #VALUE! เมื่อไฟล์เก็บสูตรนั้นเป็นสูตรธรรมดา เพราะ Excel จะเอา implicit intersection แบบเก่าไปคาบทุก operand ของ operator ตั้งแต่ v2.384.68 HotXLS Delphi Component เก็บสูตรพวก array-operator แบบเดียวกับที่ Excel 365 ทำ คือเป็นสูตร dynamic-array ในเซลล์เดียวใน XLSX และเป็นสูตร array หนึ่งเซลล์ใน XLS
อาการนี้รอดจาก code review มาได้สบาย service Delphi ของคุณเขียน workbook เสร็จ HotXLS คำนวณใหม่แล้วแคช 210 ไว้ให้ =SUM(A1:B1*{10,100}) แต่ลูกค้าเปิดใน Excel 16 กลับเห็น =SUM(@A1:B1*@{10,100}) ใน formula bar กับ #VALUE! ในเซลล์ ในไฟล์ไม่มีอะไรเสียหายเลย สิ่งที่ขาดไปคือ metadata ที่บอก Excel ว่าสูตรนี้เขียนอยู่ใต้กฎของ dynamic array และถ้าไม่มีมัน Excel จะกลับไปใช้โมเดลประเมินผลยุคก่อน dynamic array
ทำไม Excel 365 ถึงใส่ @ ให้สูตรที่ HotXLS คำนวณถูกแล้ว
Excel 365 ใส่ @ เพราะสูตรที่ไม่มีเครื่องหมาย dynamic array นิยามแล้วคือสูตร legacy และสูตร legacy จะยุบช่วงหลายเซลล์เหลือเซลล์เดียวทุกที่ที่ operator ต้องการค่าเดียว การยุบนั่นแหละคือ implicit intersection: Excel เลือกเซลล์ของช่วงที่อยู่แถวเดียวกับสูตร (ถ้าช่วงแนวตั้ง) หรือคอลัมน์เดียวกัน (ถ้าช่วงแนวนอน) และถ้าไม่มีเซลล์แบบนั้นเลย ผลลัพธ์คือ #VALUE! Excel 365 คงความหมายนี้ไว้กับสูตรสไตล์เก่า แล้วแสดง @ ให้เห็นการยุบนั้นชัด ๆ
ลองวาง =SUM(A1:B1*{10,100}) ไว้ที่ E5 แล้วการอ่านแบบ legacy จะเห็นชัดทันที A1:B1 เป็นช่วงแนวนอน สูตรอยู่คอลัมน์ E ช่วงนี้ไม่มีเซลล์ในคอลัมน์ E @A1:B1 จึงเป็น #VALUE! และ SUM ทั้งอันสืบทอดค่านั้นไป ส่วนใต้กฎ dynamic array ข้อความเดียวกันจะคูณกันทีละตัว 1 × 10 + 2 × 100 ได้ 210 เอนจินสูตรของ HotXLS ประเมินผลแบบ dynamic array มาตั้งแต่รีลีส v2.384.61 กับ v2.384.63 แค่รูปแบบไฟล์ไม่เคยบอกเรื่องนี้เท่านั้นเอง เมื่อ A1:B2 มี 1, 2, 3 กับ 4 นี่คือสูตรทดสอบกับสิ่งที่ Excel 16 แสดง:
| สูตร | ผลจาก HotXLS | Excel 16 เมื่อเก็บเป็นสูตรธรรมดา | ที่เก็บตั้งแต่ v2.384.68 |
|---|---|---|---|
=SUM(A1:B1*{10,100}) | 210 | #VALUE! | Dynamic array, Excel แสดง 210 |
=SUM((A1:B2>2)*1) | 2 | Implicit intersection, ผิดหรือ error | Dynamic array, Excel แสดง 2 |
=SUMPRODUCT((A1:B2>2)*1) | 2 | Implicit intersection, ผิดหรือ error | Dynamic array, Excel แสดง 2 |
=MAX(A1:B2-1) | 3 | Implicit intersection, ผิดหรือ error | Dynamic array, Excel แสดง 3 |
=SUM(A1:B2) | 10 | 10 | สูตรธรรมดา ไม่เปลี่ยน |
แถวสุดท้ายสำคัญพอ ๆ กับสี่แถวแรก SUM(A1:B2) ส่งช่วงเข้าพารามิเตอร์ฟังก์ชันที่รับ reference ตรง ๆ ไม่มี operator เห็นช่วงหลายเซลล์เลย intersection จึงเกิดขึ้นไม่ได้ Excel 365 เองก็เซฟสูตรนี้เป็นสูตรธรรมดา และ HotXLS ทำเช่นเดียวกัน
HotXLS เก็บสูตร array-operator ลง XLSX กับ XLS อย่างไร
HotXLS เขียนสูตร array-operator ใน XLSX เป็น dynamic array หนึ่งเซลล์ คือ element <c> แบก cm="1" สูตรเป็น <f t="array" ref="E5"> และแพ็กเกจได้ xl/metadata.xml เพิ่มมาพร้อม metadata type แบบ XLDAPR ที่ extension ของมันถือ dynamicArrayProperties fDynamic="1" attribute cm เป็นดัชนีเริ่มที่ 1 ชี้เข้าบล็อก cellMetadata ของ part นั้น และ record XLDAPR ที่อยู่หลังมันต่างหากที่บอก Excel ว่า “ประเมินตัวนี้ใต้กฎ dynamic array” โครงสร้างนี้ก็คือแบบเดียวกับที่ Excel 16 เขียนเมื่อคุณพิมพ์สูตรเดียวกันแล้วเซฟ ซึ่งเป็นวิธีที่ layout เป้าหมายถูกกำหนดขึ้นมาตั้งแรก
ใน XLS ไม่มี part สำหรับ metadata จึงเหลือ construct เดียวของ BIFF8 สำหรับการประเมิน array คือสูตร array หนึ่งเซลล์ เซลล์จะได้ record FORMULA ที่ token stream เป็น PtgExp ตัวเดียวชี้หาตัวเอง ตามด้วย record ARRAY ($0221) ที่แบกสูตรที่ parse จริงครอบช่วงหนึ่งเซลล์ Excel 365 เขียนสูตร dynamic array ลง XLS ด้วยวิธีเดียวกัน และ Excel รุ่นเก่าที่เปิดไฟล์จะเห็นเป็นสูตร array แบบ Ctrl+Shift+Enter คลาสสิก
ไม่มี API ใหม่เกี่ยวข้อง การทำเครื่องหมายเกิดตอนคุณ assign สูตรผ่าน cell API ปกติ ในทั้งสองเอนจิน ฝั่ง XLSX คือ TXLSXCell.Formula:
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Data');
Sheet.Cells[1, 1].Value := 1;
Sheet.Cells[1, 2].Value := 2;
Sheet.Cells[2, 1].Value := 3;
Sheet.Cells[2, 2].Value := 4;
// Operator บนช่วงหรือ inline array: เก็บเป็น dynamic array
Sheet.Cells[5, 5].Formula := '=SUM(A1:B1*{10,100})';
Sheet.Cells[6, 5].Formula := '=SUM((A1:B2>2)*1)';
// ช่วงส่งเข้าฟังก์ชันตรง ๆ: คงเป็น <f> ธรรมดา
Sheet.Cells[7, 5].Formula := '=SUM(A1:B2)';
if Book.Recalculate = lxOk then
Writeln(VarToStr(Sheet.Cells[5, 5].Value)); // 210
// root ของ array คงข้อความไว้โดยไม่มี '=' นำหน้า
Writeln(Sheet.Cells[5, 5].Formula); // SUM(A1:B1*{10,100})
Book.SaveAs('probe.xlsx'); // E5 กับ E6 ได้ cm="1" + t="array"
finally
Book.Free;
end;
end;
หลังแปลงแล้ว TXLSXCell.Formula คืนข้อความโดยไม่มี = รูปแบบเดียวกับที่ TXLSXRange.SetDynamicArrayFormula เก็บ โค้ดที่เทียบสตริงสูตรหลัง assign จึงควร normalize ตัว = นำหน้าเสียก่อน
เอนจินคลาสสิกทำตามกฎเดียวกันผ่าน IXLSRange.Formula บนเซลล์เดียว การ assign สูตรจะถูกย้ายเส้นทางไป path ของ array หนึ่งเซลล์เบื้องหลัง XLS ที่เซฟออกมาจึงมีคู่ FORMULA กับ ARRAY:
var
Wb: IXLSWorkbook;
Sh: TXLSWorksheet;
begin
Wb := TXLSWorkbook.Create;
Sh := Wb.Sheets.Add;
Sh.Range['A1', 'A1'].Value := 1;
Sh.Range['B1', 'B1'].Value := 2;
Sh.Range['A2', 'A2'].Value := 3;
Sh.Range['B2', 'B2'].Value := 4;
Sh.Range['E5', 'E5'].Formula := '=SUM(A1:B1*{10,100})'; // record ARRAY
Sh.Range['E6', 'E6'].Formula := '=MAX(A1:B2-1)'; // record ARRAY
Sh.Range['E7', 'E7'].Formula := '=SUM(A1:B2)'; // FORMULA ธรรมดา
Writeln(VarToStr(Sh.Range['E5', 'E5'].Value)); // 210
Writeln(VarToStr(Sh.Range['E6', 'E6'].Value)); // 3
Wb.SaveAs('probe.xls');
end;
ถ้าสิ่งที่ anchor คือผลลัพธ์หลายเซลล์ ไม่ใช่ค่ารวมสเกลาร์ API แบบระบุชัดยังเป็นเครื่องมือที่ถูกทาง คือ SetArrayFormula สำหรับสี่เหลี่ยมที่กำหนดขนาดไว้แล้ว ตามที่เล่าไว้ในสูตร dynamic array spill ด้วย HotXLS หรือ TXLSXRange.SetDynamicArrayFormula เมื่อคุณอยากได้เครื่องหมาย dynamic array ของ XLSX บนช่วงที่กำหนดขนาดเอง เส้นทางอัตโนมัติในบทความนี้ครอบคลุมแค่สูตรที่พิมพ์ลงเซลล์เดียวเท่านั้น
สูตรแบบไหนที่ HotXLS ทำเครื่องหมายเป็น dynamic array
HotXLS ทำเครื่องหมายสูตรเฉพาะเมื่อ operator มี operand subtree ที่ผลิต array ออกมา การตรวจรันบน syntax tree ที่คอมไพล์แล้ว และ operand จะผลิต array ได้ถ้ามันเป็นช่วงหลายเซลล์ array constant แบบ inline หรือนิพจน์ operator อื่นที่ตัวมันเองมี operand แบบนั้น วงเล็บโปร่งใส operator ที่นับคือตัวคณิต (+ - * / ^) การต่อสตริง (&) การเปรียบเทียบหกแบบ unary plus กับ unary minus และ percent:
A1:B1*{10,100},(A1:B2>2)*1,--(B1:B2>0)กับA1:B2-1ถูกทำเครื่องหมาย ไม่ว่าจะโผล่ตรงไหนในสูตร รวมถึงข้างใน SUMPRODUCTSUM(A1:B2)กับSUMPRODUCT(A1:A2,{1;10})ไม่ถูกทำเครื่องหมาย เพราะช่วงกับ array เดินเข้าอาร์กิวเมนต์ฟังก์ชันตรง ๆ ไม่มี operator แตะต้องเลยA1*2หรือSUM(A1,B1)*2ไม่ถูกทำเครื่องหมาย: reference หนึ่งเซลล์กับผลลัพธ์ฟังก์ชันเป็นสเกลาร์ในสายตาการตรวจนี้
ขอบเขตสามข้อนี้ตั้งใจไว้ หนึ่ง การทำเครื่องหมายเกิดเฉพาะเมื่อสูตรถูกป้อนผ่าน API หมายความว่า TXLSXCell.Formula ในเอนจิน XLSX และการ assign Formula หรือ Value บนเซลล์เดียวในเอนจินคลาสสิก สูตรที่โหลดจากไฟล์จะเขียนกลับตรงตามที่เจอ เพราะสูตร legacy จาก producer อื่นอาจพึ่ง implicit intersection ตั้งใจไว้แต่แรก สอง ข้อความที่ไม่มีทั้ง : และ { จะถูกข้ามโดยไม่คอมไพล์รอบสอง สาม สูตรที่จะ spill อย่าง =A1:B1*2 ลอย ๆ จะถูกทำเครื่องหมายเป็น dynamic array หนึ่งเซลล์ที่ anchor ไว้ตรงที่คุณวาง HotXLS ไม่ spill ให้ และ Excel จะกางผลลัพธ์ไปเซลล์ข้างเคียงตอนคำนวณใหม่รอบหน้า
กฎเรื่อง operand นี้เป็นราวพี่น้องของกฎ argument class ที่เล่าไว้ในimplicit intersection ของ defined names ใน HotXLS บทความนั้นพูดถึงพารามิเตอร์ฟังก์ชันที่ประกาศเป็น value class ส่วนบทความนี้พูดถึง operator ซึ่งในโมเดล legacy ต้องการค่าเสมอ
เอนจินคำนวณเปลี่ยนอะไรไปเพื่อให้ผลลัพธ์ตรงกัน
การแก้ฝั่งจัดเก็บใน v2.384.68 ยืนอยู่บนความจริงที่เอนจินสูตรของ HotXLS คืนค่าแบบ Excel 365 มาก่อนแล้ว ซึ่งผ่านการแก้หลายรอบในทั้งสองเอนจิน จุดที่เห็นชัดสุดคือ SUMPRODUCT: จนถึง v2.384.61 มันรับแต่ช่วงธรรมดาตั้งแต่สองช่วงขึ้นไป SUMPRODUCT((B1:B2>0)*1), SUMPRODUCT(--(B1:B2>0)) กระทั่ง SUMPRODUCT(B1:B2) ที่ให้อาร์กิวเมนต์เดียวเลยคืน #N/A ตอนนี้ HotXLS ประเมินอาร์กิวเมนต์แบบนิพจน์ทีละ element ตามกฎของ Excel:
- ทุกอาร์กิวเมนต์ต้องมีรูปร่างเท่ากันเป๊ะ สเกลาร์นับเป็น 1 × 1 ไม่งั้นผลลัพธ์คือ
#VALUE! - ค่า error ที่อยู่ข้างในอาร์กิวเมนต์ใด ๆ จะถูกคืนเป็นผลลัพธ์
- element แบบข้อความกับตรรกะนับเป็น 0 ยังต้องใช้
(B1:B2>0)*1หรือ--ครอบอยู่ดีเพื่อเปลี่ยน TRUE เป็น 1 - อาร์กิวเมนต์ที่เป็นช่วงธรรมดาล้วนยังใช้ streaming loop เดิม ช่วงใหญ่ ๆ จึงไม่ถูกแปลงเป็น array เต็มตัว
ตระกูล SUM (SUM, COUNT, AVERAGE, MIN, MAX, COUNTA) ใช้ evaluator แบบทีละ element ตัวเดียวกันเมื่ออาร์กิวเมนต์เป็นนิพจน์ operator บนช่วง =SUM((B1:B2>0)*1) จึงนับทั้งสองแถว ไม่ใช่ก้มมองแต่เซลล์แรก v2.384.62 ทำให้ space intersection operator คืนสี่เหลี่ยมร่วมของ reference สองตัว โดยคืน #NULL! เมื่อไม่ทับกัน =SUM(A1:B2 B1:B2) จึงได้ 6 ไม่ใช่ 2 และผลลัพธ์ป้อนเข้าพารามิเตอร์แบบ reference อย่าง ROWS กับ INDEX ได้ v2.384.63 เพิ่ม array constant แบบ inline อย่าง {1,2;3,4} (คอมมาคั่นคอลัมน์ เซมิโคลอนคั่นแถว) กับ reference union อย่าง (A1:B2,D4) เข้า parser การเปรียบเทียบแบบทีละ element ยังให้ element ว่างใช้ชนิดของอีกฝั่ง เป็น FALSE เมื่อเทียบกับค่าตรรกะ ตรงกับกฎสเกลาร์จาก v2.384.53 ที่เล่าไว้ในลูกโซ่การเปรียบเทียบกับเซลล์ว่างใน HotXLS
var
V: Variant;
begin
// Book คือ TXLSXWorkbook จากตัวอย่างแรก;
// active sheet ของมันมี A1:B2 = 1, 2, 3, 4
V := Book.Calculate('=SUMPRODUCT((A1:B2>2)*1)'); // 2
V := Book.Calculate('=SUMPRODUCT(A1:B2)'); // 10, อาร์กิวเมนต์เดียว
V := Book.Calculate('=SUMPRODUCT(A1:A2,{1;10})'); // 31 = 1*1 + 3*10
V := Book.Calculate('=SUM(A1:B2 B1:B2)'); // 6, ช่วงร่วม B1:B2
V := Book.Calculate('=SUM((A1:B2,B1:B2))'); // 16, ส่วนทับกันนับสองครั้ง
V := Book.Calculate('=ROWS({1,2,3;4,5,6})'); // 2
V := Book.Calculate('=TRUE*1'); // 1, เดิมเป็น -1 ก่อน v2.384.61
end;
TXLSXWorkbook.Calculate ประเมินสตริงสูตรบน active sheet โดยไม่เก็บลงไฟล์ เป็นวิธีเช็กพฤติกรรมเอนจินที่ไวมาก แต่มีข้อควรระวังกับตัว @ เอง: HotXLS รับ @ คั่นกลาง reference สองตัวเป็น binary intersection มาแต่ไหนแต่ไร และตอนนี้ประเมินรูปแบบนั้นด้วยความหมาย intersection จริง ๆ ขณะที่ใน Excel 365 @ เป็น prefix บอก implicit intersection แบบ unary อย่าเขียน @ ลงในข้อความสูตรแล้วหวังความหมายแบบ Excel ใช้ช่องว่างสำหรับ intersection แล้วปล่อยกฎการจัดเก็บด้านบนจัดการความหมาย dynamic array เอาเอง
ทำไม Excel ถึงปฏิเสธเปิดไฟล์หรือคำนวณค่าผิด
การที่ Excel ยอมรับเครื่องหมาย dynamic array ต้องผ่านการแก้สามจุดที่เทสต์ round-trip กับตัวเองไม่มีทางจับเจอ เพราะ HotXLS อ่าน output ของตัวเองถูกต้องในทุกกรณี ทุกจุดถูกค้นพบด้วยการเปิด output ของ HotXLS ใน Excel 16 แล้วเปลี่ยนตัวแปรทีละหนึ่ง:
- GUID ของ extension ต้องเป็นตัวเล็กทั้งหมด
ext uriในxl/metadata.xmlต้องเป๊ะเป็น{bdbb8cdc-fa1e-496e-a857-3c3f30c029c3}เทมเพลต HotXLS รุ่นเก่าสะกดปนตัวใหญ่ Excel 16 จึงปฏิเสธเปิดทั้งแพ็กเกจ ไม่ใช่แค่เซลล์ workbook ที่สร้างด้วยTXLSXRange.SetDynamicArrayFormulaก่อน v2.384.68 เจอปัญหาเดียวกัน - ข้อความของ array root ไม่แบก
=นำหน้า ตัวเขียน XLSX ปล่อยข้อความที่เก็บไว้ของ array root ลง<f>ตรง ๆ ถ้าเซลล์ที่แปลงแล้วยังติด=element จะอ่านได้<f t="array" ref="E5">=SUM(...)</f>ซึ่ง Excel ก็ปฏิเสธตอนเปิดเหมือนกัน HotXLS ตัดมันทิ้งตอนแปลงTXLSXCell.Formulaจึงอ่านกลับมาไม่มีตัวนั้น Double(True)ใน Delphi เป็น -1 การแปลง Variant เดินตามธรรมเนียม COM ที่ TRUE คือบิตเปิดทุกบิต และVarIsNumeric(True)ก็คืน True เหมือนกัน ก่อน v2.384.61 ทำให้=TRUE*1คืน -1 กับให้ element array แบบตรรกะถูกจัดประเภทเป็นตัวเลข การเปรียบเทียบอย่าง(B1:B2>0)=TRUEจึงเพี้ยน ตอนนี้ HotXLS เช็กvarBooleanก่อนถือ Variant เป็นตัวเลขทั้งในเลขคณิตสเกลาร์ เลขคณิต array และการจัดประเภท element ของ array และ TRUE นับเป็น 1
operand class ของ BIFF8: รายละเอียดระดับไบต์สำหรับคนเขียน format
ใน BIFF8 token operand ทุกตัวแบก operand class ของมันไว้ในไบต์ token เอง และ Excel เชื่อ class นั้นมากกว่าโครงสร้างของสูตร [MS-XLS] นิยาม class เป็นฟิลด์ PtgDataType สองบิตในบิตที่ 5 กับ 6 ของ token: 1 สำหรับ reference 2 สำหรับ value 3 สำหรับ array ห้าบิตต่ำตั้งชื่อ token ดังนั้น area reference ตัวเดียวกันมีสามการสะกด:
| Token | class แบบ reference | class แบบ value | class แบบ array |
|---|---|---|---|
PtgRef | $24 | $44 | $64 |
PtgArea | $25 | $45 | $65 |
PtgArray | $20 | $40 | $60 |
HotXLS เคยพลาดสามจุดในนี้ในที่ต่าง ๆ กัน แต่ละจุดก็ให้อาการต่างกันใน Excel ทั้งที่อ่านกลับใน HotXLS ราบรื่น:
- array constant class แบบ reference ตัว encoder เลือก class จาก context พารามิเตอร์ของ SUM กับ ROWS เป็น reference class
=SUM({1,2})จึงถูกเขียนด้วยPtgArrayเป็น$20แล้ว Excel แสดงสูตรทั้งอันเป็น=#N/Aarray constant เป็น reference ไม่ได้เด็ดขาด ตั้งแต่ v2.384.63 HotXLS จึงเขียน class แบบ array$60ทุกที่ที่ context เรียกหา reference - operand ของ
PtgIsectกับPtgUnionเป็น class แบบ value operator ทวิภาครับ operand แบบ value ซึ่งถูกกับ*แต่ผิดกับ reference operator พอเป็น area$45นำหน้าPtgIsect($0F) Excel จะอ่าน=SUM(A1:B2 B1:B2)เป็น=SUM(@A1:B2 @B1:B2)แล้วคืน#VALUE!ตั้งแต่ v2.384.62 operand ของPtgIsectกับPtgUnion($10) ถูกเขียนเป็น reference class คือ$25 - operand แบบ value ข้างใน record ARRAY Excel ใช้ implicit intersection แม้ข้างในสูตร array เมื่อ operand เป็น value class HotXLS เขียน
$45ตรงนั้น สูตร array หนึ่งเซลล์ของ=SUM(A1:B1*{10,100})จึงประเมินได้ 10 ใน Excel ตั้งแต่ v2.384.68 token stream ของ record ARRAY จะเลื่อน reference ทุกตัวที่เป็น value class กับ array constant ขึ้นเป็น array class คือ$65กับ$60ซึ่งตรงกับที่ Excel เขียน
reader ที่เมิน class bits จะ round-trip ทั้งสามกรณีได้สบาย ถ้าคุณดูแล BIFF8 writer ของตัวเองอยู่ เทียบ class bits ของ token operand ทุกตัวกับไฟล์ที่ Excel เซฟจากสูตรเดียวกัน อย่าเทียบแค่หมายเลข token
สรุปไว้เช็กเร็ว ๆ
- Excel 365 แสดง
@เมื่อ operator ในสูตรธรรมดาที่ไม่มีเครื่องหมายได้รับช่วงหลายเซลล์หรือ inline array - HotXLS v2.384.68 ขึ้นไปเก็บสูตรแบบนี้เป็น dynamic array หนึ่งเซลล์ใน XLSX (
cm="1",t="array", metadataXLDAPR) และเป็นสูตร array หนึ่งเซลล์ใน XLS (FORMULA ที่มีPtgExpคู่กับ ARRAY$0221) - นับเฉพาะ operand ของ operator ช่วงที่ส่งตรงเข้าอาร์กิวเมนต์ฟังก์ชันยังเป็นสูตรธรรมดา
- ทำเครื่องหมายเฉพาะสูตรที่ป้อนผ่าน
TXLSXCell.FormulaหรือFormula/Valueแบบเซลล์เดียวฝั่งคลาสสิก สูตรที่โหลดมาไม่ถูกแตะ - เซลล์ root ที่แปลงแล้วอ่านกลับมาไม่มี
=นำหน้า - GUID ของ
ext uriสำหรับ dynamic array ต้องเป็นตัวเล็ก ไม่งั้น Excel ปฏิเสธทั้งแพ็กเกจ - ใน Delphi
Double(True)เป็น -1 เช็กvarBooleanก่อนแปลงเป็นตัวเลข - BIFF8: array constant ไม่มีวันเป็น reference class, operand ของ
PtgIsect/PtgUnionเป็น reference class, operand ใน record ARRAY เป็น array class
HotXLS อ่าน เขียน และคำนวณ workbook XLS กับ XLSX จาก Delphi กับ C++Builder โดยตรง และเก็บสูตร array-operator ไว้ให้ Excel 365 เปิดแล้วเจอค่าเดียวกับที่ HotXLS คำนวณ ดูHotXLS Delphi spreadsheet component สำหรับรุ่น เอกสาร และดาวน์โหลดรุ่นทดลอง