Commit Graph

30 Commits

Author SHA1 Message Date
Brandon Thomas dcd469d12e Merge pull request #419 from neilcawse/fix/function-fixes
Functions: MODE/VALUE/SUMPRODUCT text args, ADDRESS [Book]Sheet, Excel-style lookup bisection, SUBTOTAL skips filtered rows

# Conflicts:
#	docs/file-format-parity.md
#	docs/function-parity.md
2026-10-11 18:05:41 -04:00
Shirish Veerabattini 478f01d236 functions: keep DATEDIF's original description, add the units
Restores "Counts whole years, months or days between two dates." and
appends the units, in the multi-line form rustfmt gives a long entry.

Drops the 256-argument test case: the parser already refuses more than
255 arguments, so it never reached the entry check. (993c108's message
says entry "also refuses" them; it already did.)

Claude-Session: https://claude.ai/code/session_01BGBBGBC2gRpQ7GDuwQ6WPt
2026-10-11 15:57:33 -05:00
neilcawse 53690bace0 Functions: oracle fixes - MODE/VALUE/SUMPRODUCT text args, ADDRESS [Book]Sheet, Excel-style lookup bisection, SUBTOTAL skips filtered rows (table filter criteria kept)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:39:31 -04:00
Shirish Veerabattini 993c108660 functions: share the argument-count rule between call and entry
FnSpec::accepts holds the arity check (including Excel's 255-argument
limit) that functions::call and the entry check both use, so entry also
refuses 256 arguments to a variadic function. The evaluator's own check
in calc is unchanged.

Claude-Session: https://claude.ai/code/session_01BGBBGBC2gRpQ7GDuwQ6WPt
2026-10-11 15:37:09 -05:00
Shirish Veerabattini 9b59966cc9 Merge upstream main into fix/formula-arg-count-363
Claude-Session: https://claude.ai/code/session_01BGBBGBC2gRpQ7GDuwQ6WPt
2026-10-11 15:20:56 -05:00
Shirish Veerabattini 56f784473d engine: refuse formulas with the wrong argument count on entry
A formula calling a built-in function with too few or too many
arguments (=DATEDIF(a,b), =SUM(), =DATE(2024,1)) was stored and showed
#VALUE! with no hint about what was wrong. Entry now refuses it with
the function's signature, e.g. "DATEDIF has too few arguments. Use
DATEDIF(start_date, end_date, unit)", and leaves the cell unchanged.

Only registered functions are checked: special forms the evaluator
handles itself (IF, LET, LAMBDA...), LAMBDA names and unknown functions
are skipped. Paste and Find & Replace keep turning input that fails
entry into text. DATEDIF's description now lists its units.

Fixes #363

Claude-Session: https://claude.ai/code/session_01BGBBGBC2gRpQ7GDuwQ6WPt
2026-10-11 13:29:14 -05:00
Brandon Thomas 5360a77505 Merge remote-tracking branch 'origin/main' into fix-325
# Conflicts:
#	ROADMAP.md
#	crates/engine/src/cmd/mod.rs
#	crates/ui-egui/src/dialogs.rs
#	docs/gaps.md
#	docs/parity-checklist.md
#	docs/ui-parity.md
2026-10-11 13:12:41 -04:00
Brandon Thomas 867c5fd1f1 Merge pull request #317 from vangel76/feat/forecast-ets
Functions: FORECAST.ETS, .CONFINT, .SEASONALITY and .STAT (#314)

# Conflicts:
#	README.md
#	ROADMAP.md
#	docs/function-parity.md
#	docs/gaps.md
2026-10-11 12:05:55 -04:00
Brandon Thomas c2c381d55d Merge pull request #292 from vangel76/feat/bahttext
Functions: BAHTTEXT (#291)

# Conflicts:
#	README.md
#	ROADMAP.md
#	docs/function-parity.md
#	docs/gaps.md
#	docs/target-app-parity.md
2026-10-11 11:44:48 -04:00
Brandon Thomas fa4d2e5741 Merge pull request #280 from vangel76/feat/percentof-euroconvert
Functions: PERCENTOF and EUROCONVERT (#279)

# Conflicts:
#	README.md
#	ROADMAP.md
#	crates/functions/src/financial.rs
#	docs/function-parity.md
#	docs/gaps.md
#	docs/target-app-parity.md
2026-10-11 11:44:46 -04:00
Brandon Thomas f91f7bc154 Merge pull request #276 from vangel76/feat/odd-bond-functions
Functions: ODDFPRICE, ODDFYIELD, ODDLPRICE, ODDLYIELD (#275)

# Conflicts:
#	ROADMAP.md
2026-10-11 11:43:49 -04:00
vangel76 c2d2f22e6a Data: Forecast Sheet (data.forecastSheet) (#322)
New sheet before the active one with the sorted history, a FORECAST.ETS
forecast column, FORECAST.ETS.CONFINT lower/upper bounds, an optional
FORECAST.ETS.STAT table, an Excel table over the data and a line or column
chart. Parameters: timeline/values (default: the selection's first two
columns), end or periods, confidence interval, seasonality, missing-point
fill, duplicate aggregation, statistics, chart kind. Data ribbon button
opens a form dialog; programmatic calls never do. gridcraft-functions
exports Timeline to continue a timeline (numeric step or calendar months).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:54:30 +02:00
vangel76 a1dc03a484 Functions: FORECAST.ETS, .CONFINT, .SEASONALITY and .STAT (#314)
AAA exponential smoothing (Hyndman et al. 2008, ETS(A,A,A)) over an evenly
spaced timeline: documented seasonality, data completion and aggregation
arguments, calendar-month date timelines, autocorrelation seasonality
detection, state-space prediction intervals, statistic types 1-8.
Smoothing parameters minimise the in-sample SSE (grid + clamped
Nelder-Mead). XLSX export treats the family's range arguments as
array-taking (no spurious dynamic-array metadata), FORECAST/FORECAST.LINEAR
included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:42:52 +02:00
vangel76 35d6c23fd1 Functions: BAHTTEXT (#291)
Thai number words with บาท / สตางค์ / ถ้วน, rounded to satang, ล้าน
repeating per six digits, ลบ for negatives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:07:41 +02:00
vangel76 a2894eff83 Functions: PERCENTOF and EUROCONVERT (#279)
PERCENTOF = SUM(subset) / SUM(all), written as _xlfn.PERCENTOF.
EUROCONVERT uses the fixed EU conversion rates for the currencies the
function documents, with currency rounding and triangulation precision.
New Add-in category for EUROCONVERT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:56:18 +02:00
vangel76 fed3996944 Functions: ODDFPRICE, ODDFYIELD, ODDLPRICE, ODDLYIELD (#275)
Odd first and last coupon periods via quasi-coupon periods, from the
formulas on Microsoft's public function pages. ODDLYIELD is closed form;
ODDFYIELD bisects ODDFPRICE (bounded). Financial is now 55 / 55.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:51:34 +02:00
nihilist-narwhal d75b6b73b4 Criteria: text patterns don't match blank cells
COUNTIF(range,"*") counted empty cells and COUNTIF(range,"<>*") skipped
them. Excel matches text patterns against text only: an empty string is
text, a blank cell is not.
2026-10-10 22:52:58 -05:00
Brandon Thomas 960c79a328 Merge pull request #187 from MarekSpodBiedry/issue-57
Compare numbers at 15 significant digits like Excel (#57)
2026-10-10 15:46:33 -04:00
Brandon Thomas 5578c09e55 Merge pull request #159 from itrycodinganditsbad/fix-min-no-args
functions: MIN() accepts zero arguments
2026-10-10 15:35:43 -04:00
MarekSpodBiedry 0f21244e9e Compare numbers at 15 significant digits like Excel (#57) 2026-10-10 19:08:16 +02:00
Brandon Thomas 277f01265b Merge fixed #38 (font fallback) into feat/i18n-language
# Conflicts:
#	crates/ui-egui/src/grid.rs
#	crates/ui-egui/src/ribbon.rs
#	crates/ui-egui/src/widgets.rs
2026-10-10 12:31:57 -04:00
Brandon Thomas 7f9d77a12a Merge pull request #60 from MarekSpodBiedry/issue-58
ROUND: keep 15 significant digits when asked for 15+ digits, so ROUND(0.1+0.2,15) is 0.3 (#58)
2026-10-10 11:57:14 -04:00
Brandon Thomas 882a19568e Merge pull request #68 from LloydNicholson/fix/date-1900-feb-29
Issue #124: DATE(1900,2,29) is serial 60, as in Excel
2026-10-10 11:57:14 -04:00
Fabio Lussi 61b264ac21 functions: MIN() accepts zero arguments
Excel documents the first argument of MIN as optional and returns 0
when there are no numbers. MIN was registered with a minimum of one
argument, so =MIN() returned #VALUE!. The aggregate already returns 0
for an empty list, so only the registered arity needed to change.

Fixes #138

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:26:36 +02:00
lloyd 8fe3276cb8 SUMIF/AVERAGEIF size the sum range like the criteria range
Excel adds up the range that starts at sum_range's top-left cell and has
the criteria range's height and width, whatever size sum_range has. With
A1:B3 = a,1 / b,2 / a,3, =SUMIF(A1:A3,"a",B1) is 4; GridCraft gave 1,
because if_numbers only saw sum_range's values (one cell) and read them
at the criteria positions. Its comment promised the resizing, but by then
the argument was a plain value.

The evaluator has the references, so it now does the resizing, as it does
for OFFSET and INDEX: for SUMIF and AVERAGEIF with three arguments, a
single-area sum range reference becomes resized_sum_range(sum, criteria
height, criteria width), cut off at the sheet's edge. It works across
sheets and through names. The dependency graph records the resized
range, so editing a cell inside it recalculates; when the criteria range
isn't a plain reference the formula is recalculated every time instead.
SUMIFS and AVERAGEIFS keep requiring same-size ranges (#VALUE!
otherwise).

Tests: a single-cell, shorter, wider, longer and horizontal sum range,
whole-column criteria, editing a cell of the resized range, the sheet
edge, another sheet and a defined name, and SUMIFS/AVERAGEIFS size
mismatches. They fail before the fix (1 instead of 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:44:18 +02:00
lloyd ecaead4c12 DATE(1900,2,29) is serial 60, as in Excel
serial_from_ymd worked out the real calendar date and then moved serials
up to 60 down by one. February 1900 has no 29th in the real calendar, so
DATE(1900,2,29) and DATEVALUE("2/29/1900") rolled over to 1 March (61),
and DATE(1900,3,0) gave 28 February (59). Excel's 1900 system keeps the
Lotus leap day: serial 60 is 29 February 1900, and DAY(60) already
said so.

The serial of the first of the month is now taken in Excel's calendar
(one lower up to February 1900), and the day, with any overflow, counts
on from there. So day and month overflow pass through serial 60 both
ways: DATE(1900,1,60), DATE(1900,4,-31) and DATE(1901,-10,29) are 60 too.
Month and day arithmetic is checked, so hostile values return None.

days_in_month_in gives February 1900 29 days in the 1900 system; EDATE
and EOMONTH use it, so EOMONTH(60,0) is 60 rather than the day before,
and the text parser uses it in place of its own special case. The 1904
system is unchanged (1900 is before its epoch).

Tests: serials around the leap day (overflow both ways, every serial
0-366 round-trips through its year, month and day), hostile months and
days, parsing 2/29/1900 and 1900-02-29, DATE/DATEVALUE/EDATE/EOMONTH
in the 1900 system and the 1904 system. The DATE, DATEVALUE and serial
cases fail before the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:36:08 +02:00
MarekSpodBiedry 2c2d92ef6b ROUND: keep 15 significant digits when asked for 15+ digits, so ROUND(0.1+0.2,15) is 0.3 (#58) 2026-10-09 19:29:29 +02:00
justinjohn0306 7e24aa398a CI: satisfy newer clippy lints (toolchain drift, behavior-preserving)
Local clippy 1.96 flags pre-existing patterns the code has always had:
manual range-contains comparisons, a dead `|| true` filter and a
simplifiable boolean. None change behaviour; they only clear the lints
so `cargo xtask ci` passes on a current toolchain.
2026-10-09 13:08:28 +05:30
Brandon Thomas 488e12de31 Rename SheetCraft to GridCraft everywhere (crates, apps, bundle ids, packaging, docs)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 01:39:08 -04:00
Brandon Thomas 1ba9daa263 M0: foundation — core, numfmt, formula, functions (478), model, calc, xlsx/csv, engine
Clean-room spreadsheet engine: addresses/values/dates, Excel number formats,
formula parser/printer/reference adjustment, worksheet function library,
copy-on-write workbook model, dependency-driven recalculation with dynamic
arrays, XLSX/CSV I/O, and a command engine (cell entry, clipboard, fill,
formatting, sheets, sort/filter, tables, charts, CF, names, view, file).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:18:10 -04:00