LEN of an emoji, MOD(0.3, 0.1), SUM over an array expression and text functions of a date cell differ on purpose. FILTER and SORT came back #NAME? because Sheets doesn't read Excel's _xlfn._xlws. prefix, which the writer already writes: -update-from leaves such rows unconfirmed. A number downloaded as "110." is read as 110.
- commit
- d3925e8450120cd909bc3ad75c4f24057da62e03
- parent
- 441dcbc9dff48f7dbccb3c676de100049c367bf8
- author
- Dami <dami.etoile@gmail.com>
- kind
- fix risk score 0.9 of 3, Jev's estimate
| M | docs/ | 7 | ++- |
| M | internal/ | 18 | ++++++-- |
| M | internal/ | 76 | +++++++++++++++--------------- |
@@ -134,7 +134,12 @@ they agree (for two errors, compare C and F by eye). The exporter saves
134134 formulas Excel has nothing like (`SPLIT`, `REGEXMATCH`, `SORTN`,135135 `FLATTEN`, `ARRAYFORMULA` inside a formula) as values, so those rows136136 leave C empty and say so in the note: type the formula from B into C137-before downloading, or `-update-from` leaves them unconfirmed.137+before downloading, or `-update-from` leaves them unconfirmed. It also138+leaves a row unconfirmed where Sheets says `#NAME?` and 012 doesn't: the139+import didn't recognize a function, as Sheets doesn't read `FILTER` and140+`SORT` written with the `_xlfn._xlws.` prefix Excel gives them. A number141+Sheets downloads with a trailing point (`110.` for `=100*1.1`) is read142+without it.138143 139144 ## The XLSX differential test140145 @@ -6,6 +6,7 @@ import (
66 "os"77 "slices"88 "strconv"9+ "strings"910 "testing"1011 1112 "github.com/FelineStateMachine/012/internal/sheet"@@ -59,8 +60,8 @@ func excelKeeps(formula string) bool {
5960 6061 // foldSheets records Sheets' results from name, a CSV download of the6162 // corpus sheet after importing the XLSX workbook into Sheets. Rows left62-// out of the download, and formulas saved as values that weren't typed63-// in, stay as they are.63+// out of the download, formulas saved as values that weren't typed in,64+// and functions the import didn't recognize stay as they are.6465 func foldSheets(t *testing.T, rows []*corpusLine, name string) {6566 t.Helper()6667 f, err := os.Open(name)@@ -78,13 +79,22 @@ func foldSheets(t *testing.T, rows []*corpusLine, name string) {
7879 sheets := map[string]string{}7980 for _, rec := range recs[1:] {8081 if len(rec) > colKind {81- sheets[rec[colFormula]] = encodeResult(rec[colText], rec[colKind])82+ text := rec[colText]83+ if rec[colKind] == "number" {84+ // Sheets writes a number a hair off a whole one, such as85+ // 100*1.1, as "110." when it rounds to 15 digits.86+ text = strings.TrimSuffix(text, ".")87+ }88+ sheets[rec[colFormula]] = encodeResult(text, rec[colKind])8289 }8390 }8491 skipped := 08592 for _, r := range rows {8693 got, ok := sheets[r.formula]87- if !ok || (got == "(blank)" && !excelKeeps(r.formula)) {94+ // #NAME? where 012 knows every name means the import didn't95+ // recognize a function (Sheets reads Excel's _xlfn._xlws.FILTER96+ // so): the formula wasn't computed.97+ if !ok || (got == "(blank)" && !excelKeeps(r.formula)) || (got == "#NAME?" && r.ours != got) {8898 skipped++8999 continue90100 }@@ -58,13 +58,13 @@
5858 =LEFT(12345, 2) "12" "12"5959 =LEFT(TRUE, 1) "T" "T"6060 =MID("abc", 5, 2) "" ""61-=MID("abc", 0, 2) #VALUE! #NUM!62-=LEN(Data!F1) 5 961+=MID("abc", 0, 2) #NUM! #NUM!62+=LEN(Data!F1) 5 9 on purpose: F1 is a date, stored in the XLSX as its serial with a m/d/yyyy format; Sheets' text functions read a date cell as it shows (9/26/2026), 012 reads its serial, as Excel does6363 =LEN(1/4) 4 46464 =LEN("héllo") 5 565-=LEN("😀") 1 265+=LEN("😀") 1 2 on purpose: LEN counts characters (code points), where Sheets counts UTF-16 units6666 =LEFT("日本語", 2) "日本" "日本"67-=UPPER("straße") "STRAßE" "STRASSE"67+=UPPER("straße") "STRASSE" "STRASSE"6868 =UPPER(1.5) "1.5" "1.5"6969 =PROPER("hello wORLD it's") "Hello World It'S" "Hello World It'S"7070 =PROPER("o'neil mcdonald 3rd") "O'Neil Mcdonald 3Rd" "O'Neil Mcdonald 3Rd"@@ -92,7 +92,7 @@
9292 =REGEXMATCH("ABC", "(?i)abc") TRUE unconfirmed9393 =REGEXMATCH(123, "1") #VALUE! unconfirmed9494 =REGEXEXTRACT("abc", "\d+") #N/A unconfirmed95-=TEXTJOIN("|", TRUE, REGEXEXTRACT("a1b2", "(\d)b(\d)")) "1" unconfirmed95+=TEXTJOIN("|", TRUE, REGEXEXTRACT("a1b2", "(\d)b(\d)")) "1|2" unconfirmed9696 =REGEXREPLACE("john smith", "(\w+) (\w+)", "$2, $1") "smith, john" "smith, john"9797 =COLUMNS(SPLIT("a,b,,c", ",", TRUE, FALSE)) 4 unconfirmed9898 =SUM(SPLIT("1,2,3", ",")) 6 unconfirmed@@ -108,10 +108,10 @@
108108 =TEXT(2.5, "0") "3" "3"109109 =TEXT(-0.5, "0") "-1" "-1"110110 =TEXT(1.005, "0.00") "1.01" "1.01"111-=TEXT(0.75, "# ?/?") " /1" "3/4"111+=TEXT(0.75, "# ?/?") "3/4" "3/4"112112 =TEXT("abc", "0") "abc" "abc"113113 =TEXT("12", "0.00") "12.00" "12.00"114-=TEXT(TRUE, "0") "1" "TRUE"114+=TEXT(TRUE, "0") "TRUE" "TRUE"115115 =TEXT(Data!F1, "dddd") "Saturday" "Saturday"116116 =TEXT(Data!F1, "mmm d, yyyy") "Sep 26, 2026" "Sep 26, 2026"117117 =TEXT(Data!F1, "dddd, mmmm d, yyyy") "Saturday, September 26, 2026" "Saturday, September 26, 2026"@@ -126,7 +126,7 @@
126126 =ROUND(0.285, 2) 0.29 0.29127127 =ROUND(-2.5) -3 -3128128 =ROUND(1.5, 0.9) 2 2129-=ROUND(1.23456789012345, 14) 1.23456789012345 1.23456789012346129+=ROUND(1.23456789012345, 14) 1.23456789012346 1.23456789012346130130 =ROUNDUP(0.1+0.2, 1) 0.3 0.3131131 =ROUNDDOWN(1-0.9, 1) 0.1 0.1132132 =TRUNC(1234, -2) 1200 1200@@ -141,26 +141,26 @@
141141 =FLOOR(-2.5, -1) -2 -2142142 =MOD(-7, 3) 2 2143143 =MOD(10, 0) #DIV/0! #DIV/0!144-=MOD(0.3, 0.1) 0.1 -5.55111512312578E-17144+=MOD(0.3, 0.1) 0.1 -5.55111512312578E-17 on purpose: 012 computes n - d*INT(n/d) as Excel does, keeping the divisor's sign; Sheets' -5.55E-17 is a floating artefact145145 =QUOTIENT(-7, 2) -3 -3146146 =0.1+0.2 0.3 0.3147-=0.1+0.2=0.3 FALSE TRUE147+=0.1+0.2=0.3 TRUE TRUE148148 =0.1+0.2-0.3 5.55111512312578E-17 5.55111512312578E-17149149 =1-0.9 0.1 0.1150-=(1-0.9)=0.1 FALSE TRUE151-=SUM(0.1, 0.2, 0.3)=0.6 FALSE TRUE150+=(1-0.9)=0.1 TRUE TRUE151+=SUM(0.1, 0.2, 0.3)=0.6 TRUE TRUE152152 =1/3 0.333333333333333 0.333333333333333153-=100*1.1 110 110.153+=100*1.1 110 110154154 =1E+21 1E+21 1E+21155-=123456789012345678 123456789012346000 123456789012345000155+=123456789012345678 123456789012345000 123456789012345000156156 =9007199254740993 9007199254740990 9007199254740990157157 =0.000001 0.000001 0.000001158158 =-0 0 0159159 =-2^2 4 4160160 =0^0 1 1161161 =POWER(0, 0) 1 1162-=POWER(-8, 1/3) #NUM! -2163-=(-8)^(1/3) #NUM! -2162+=POWER(-8, 1/3) -2 -2163+=(-8)^(1/3) -2 -2164164 =1E+308*10 #NUM! #NUM!165165 =LOG(10, 1) #DIV/0! #DIV/0!166166 ## Errors and their propagation@@ -171,7 +171,7 @@
171171 =NA()+1/0 #N/A #N/A172172 =1/0+NA() #DIV/0! #DIV/0!173173 =IFERROR(1/0, "x") "x" "x"174-=IFERROR(NA()) "" (blank)174+=IFERROR(NA()) (blank) (blank)175175 =IFERROR(Data!J2, Data!J4) #N/A #N/A176176 =IFNA(1/0, 0) #DIV/0! #DIV/0!177177 =ISERR(NA()) FALSE FALSE@@ -180,13 +180,13 @@
180180 =AND(TRUE, 1/0) #DIV/0! #DIV/0!181181 =OR(TRUE, NA()) #N/A #N/A182182 =Data!A1+nosuchname #NAME? #NAME?183-=CHOOSE(0, "a") #VALUE! #NUM!184-=INDEX(Data!A1:A3, 5) #REF! #NUM!183+=CHOOSE(0, "a") #NUM! #NUM!184+=INDEX(Data!A1:A3, 5) #NUM! #NUM!185185 =AVERAGEIF(Data!A1:A7, ">100") #DIV/0! #DIV/0!186186 =SWITCH(Data!B4, "north", 1) #N/A #N/A187187 =PMT(0, 0, 1000) #NUM! #NUM!188-=COUNT(Data!J1:J4) #DIV/0! 0189-=COUNTA(Data!J1:J4) #DIV/0! 4188+=COUNT(Data!J1:J4) 0 0189+=COUNTA(Data!J1:J4) 4 4190190 ## Coercion191191 =" 3 "*1 3 3192192 ="1,000"*1 1000 1000@@ -213,13 +213,13 @@
213213 =TRUE="TRUE" FALSE FALSE214214 =Data!A5<"a" TRUE TRUE215215 =""=0 FALSE FALSE216-=IF("TRUE", 1, 2) #VALUE! 1216+=IF("TRUE", 1, 2) 1 1217217 =IF(Data!A5, "y", "n") "n" "n"218218 =AND("abc") #VALUE! #VALUE!219219 =AND(Data!A4) #VALUE! #VALUE!220-=NOT("") #VALUE! TRUE220+=NOT("") TRUE TRUE221221 =ROUND("2.5") 3 3222-=LEFT(Data!F1, 3) "462" "9/2"222+=LEFT(Data!F1, 3) "462" "9/2" on purpose: F1 is a date, stored in the XLSX as its serial with a m/d/yyyy format; Sheets' text functions read a date cell as it shows (9/26/2026), 012 reads its serial, as Excel does223223 ## Lookups and criteria224224 =VLOOKUP("B*", Data!D1:E3, 2, FALSE) 2.5 2.5225225 =VLOOKUP("Blueberry", Data!D1:E3, 2) 2.5 2.5@@ -236,9 +236,9 @@
236236 =MATCH(35, Data!I1:I3, -1) #N/A #N/A237237 =MATCH(3, {1, 3, 5}, 0) 2 2238238 =INDEX(Data!C1:E1, 2) "Apple" "Apple"239-=INDEX(Data!D1:E3, 4, 1) #REF! #NUM!240-=ROWS(INDEX(Data!A1:C3, 0, 1)) 1 3241-=SUM(INDEX(Data!C1:E3, 0, 1)) 1 6239+=INDEX(Data!D1:E3, 4, 1) #NUM! #NUM!240+=ROWS(INDEX(Data!A1:C3, 0, 1)) 3 3241+=SUM(INDEX(Data!C1:E3, 0, 1)) 6 6242242 =XLOOKUP("kiwi", Data!D1:D3, Data!E1:E3) #N/A #N/A243243 =XLOOKUP("Apple", Data!D1:D5, Data!E1:E5, , 0, -1) 9 9244244 =XLOOKUP(3.2, Data!C1:C7, Data!D1:D7, , -1) "Cherry" "Cherry"@@ -254,19 +254,19 @@
254254 =COUNTIF(Data!B1:B7, "<>north") 4 4255255 =COUNTIF(Data!A1:A7, "") 2 2256256 =COUNTIF(Data!A1:A7, "10") 1 1257-=COUNTIF(Data!J1:J4, 3) 0 1257+=COUNTIF(Data!J1:J4, 3) 1 1258258 =COUNTIF(Data!E1:E7, TRUE) 1 1259259 =COUNTIF(Data!F1:F5, ">" & DATE(2026, 1, 1)) 2 2260260 ## Arrays and spills261261 =SUM(SEQUENCE(3, 3, 0, 2)) 72 72262262 =INDEX(SEQUENCE(3, 2, 10, 5), 3, 2) 35 35263-=ROWS(SEQUENCE(0)) 1 #NUM!264-=ROWS(FILTER(Data!A1:A7, Data!C1:C7>3)) 4 #NAME?265-=TEXTJOIN(",", TRUE, FILTER(Data!D1:D5, Data!B1:B5="north")) "Apple,Cherry,Apple" #NAME?266-=FILTER(Data!A1:A3, Data!A1:A3>100) #N/A #NAME?267-=TEXTJOIN(",", TRUE, SORT(Data!C1:C5, 1, FALSE)) "5,4,3,2,1" #NAME?268-=INDEX(SORT(Data!D1:E5, 2, FALSE), 1, 1) "date" #NAME?269-=TEXTJOIN(",", TRUE, SORT(Data!D1:D5)) "Apple,Apple,Banana,Cherry,date" #NAME?263+=ROWS(SEQUENCE(0)) #NUM! #NUM!264+=ROWS(FILTER(Data!A1:A7, Data!C1:C7>3)) 4 unconfirmed Sheets read _xlfn._xlws.FILTER and _xlfn._xlws.SORT, which is how Excel writes them, as #NAME?: confirm on the next import265+=TEXTJOIN(",", TRUE, FILTER(Data!D1:D5, Data!B1:B5="north")) "Apple,Cherry,Apple" unconfirmed Sheets read _xlfn._xlws.FILTER and _xlfn._xlws.SORT, which is how Excel writes them, as #NAME?: confirm on the next import266+=FILTER(Data!A1:A3, Data!A1:A3>100) #N/A unconfirmed Sheets read _xlfn._xlws.FILTER and _xlfn._xlws.SORT, which is how Excel writes them, as #NAME?: confirm on the next import267+=TEXTJOIN(",", TRUE, SORT(Data!C1:C5, 1, FALSE)) "5,4,3,2,1" unconfirmed Sheets read _xlfn._xlws.FILTER and _xlfn._xlws.SORT, which is how Excel writes them, as #NAME?: confirm on the next import268+=INDEX(SORT(Data!D1:E5, 2, FALSE), 1, 1) "date" unconfirmed Sheets read _xlfn._xlws.FILTER and _xlfn._xlws.SORT, which is how Excel writes them, as #NAME?: confirm on the next import269+=TEXTJOIN(",", TRUE, SORT(Data!D1:D5)) "Apple,Apple,Banana,Cherry,date" unconfirmed Sheets read _xlfn._xlws.FILTER and _xlfn._xlws.SORT, which is how Excel writes them, as #NAME?: confirm on the next import270270 =TEXTJOIN(",", TRUE, UNIQUE(Data!B1:B5)) "north,south,east" "north,south,east"271271 =TEXTJOIN(",", TRUE, UNIQUE(Data!G1:G4, FALSE, TRUE)) "1,3" "1,3"272272 =TEXTJOIN(",", TRUE, FLATTEN(Data!C1:D2)) "1,Apple,2,Banana" unconfirmed@@ -277,15 +277,15 @@
277277 =ROWS(CHOOSEROWS(Data!A1:A7, 1, 3)) 2 2278278 =SUM(ARRAYFORMULA(Data!C1:C3*Data!G1:G3)) 11 unconfirmed279279 =ARRAYFORMULA(SUM(Data!C1:C3^2)) 14 14280-=SUM(Data!C1:C3*2) 12 #VALUE!281-=SUMPRODUCT(Data!C1:C3>1) 0 2280+=SUM(Data!C1:C3*2) 12 #VALUE! on purpose: 012 computes the arguments of functions that take ranges as arrays, as Excel 365 does; Sheets needs ARRAYFORMULA281+=SUMPRODUCT(Data!C1:C3>1) 2 2282282 =SUMPRODUCT(Data!A1:A4, Data!C1:C4) 140 140283283 =SUM({1, 2; 3, 4}) 10 10284284 =INDEX({1, 2; 3, 4}, 2, 1) 3 3285285 ## LET and LAMBDA286286 =LET(x, 2, y, x+1, x*y) 6 6287287 =LET(x, SEQUENCE(3), SUM(x)) 6 6288-=LET(x, 1/0, 5) #DIV/0! 5288+=LET(x, 1/0, 5) 5 5289289 =LAMBDA(x, x+1)() #N/A #N/A290290 =LET(f, LAMBDA(x, x+1), f(f(1))) 3 3291291 =SUM(MAP({1, 2, 3}, LAMBDA(x, x*10))) 60 60