Repository navigation
solve-engine 2.40.0
A note can now check itself, ask what a line would say with a different input, and explain where a number came from. Plain decimals and whole numbers past 2^53 are exact, and units multiply, divide and cancel the way numbers do. Twenty-one features land together with the fixes from reviewing them, including a cross-feature review of the whole release before it was cut.
This is a minor release. Nothing a host calls has been removed or renamed. The answers that move are listed under Answers that change: each was wrong before, and is now right or a named error.
A note that checks and explains itself
Checks (#506). A line starting check states something the note must keep true. It passes quietly and fails loudly when an edit breaks it, and only a line written with check counts (#594); ≈ and within make it approximate, and a host reads the pass and fail count from parseDocument and evaluateDocument. A check compares the way the comparison operators do, so an exact decimal, money and a whole number past 2^53 are checked exactly, and a failure shows its two sides to as many places as it takes to tell them apart.
| line | 2.39 | 2.40 |
|---|---|---|
check 1 + 1 == 2 |
parse error | ✓ |
check 22/7 ≈ pi within 0.1% |
parse error | ✓ (differs by 0.04%) |
check 2^53 + 1 > 2^53 |
parse error | ✓ |
check 1.845 == 1.85 |
parse error | check failed: 1.845 is not equal to 1.850 |
What-if and sweeps (#505). line 4 with deposit = 150000 re-runs line 4 as though an input were different, through every line in between, and line 4 for rate from 3% to 6% step 1% lists its answers across a range. The note itself is untouched, and a host gets the same through engine.whatIf(text, overrides).
deposit = 100000 100,000
rate = 5% 5.00%
loan = 400000 - deposit 300,000
loan * rate 15,000
line 4 with deposit = 150000 12,500
line 4 for rate from 3% to 6% step 1% [9,000, 12,000, 15,000, 18,000]
Explain and trace (#522). explainLine now derives conversions, function calls and finance phrases, through an explain hook any package can implement. inputs of line N (or engine.traceLine(n) for a host) lists the lines a result came from.
| line | steps |
|---|---|
5 km in miles |
1 km is 0.621371 miles; 5 times 0.621371, 3.11 miles |
sqrt(16) + 2 |
the square root of 16, 4; 4 plus 2, 6 |
Sections and tags (#508). total of section "Travel" totals the lines under a heading by its name, and total by tag breaks the note down by every tag at once, with each tag's share.
Numbers that are exact
Exact decimals (#511). A number written with a decimal point keeps the exact decimal it was written as, the way money always has, so a comparison agrees with the answer on screen. What an ordinary answer shows does not change. Irrational results (roots, logarithms, trigonometry, pi), scientific notation and units other than money stay in floating point.
| expression | 2.39 | 2.40 |
|---|---|---|
0.1 + 0.2 == 0.3 |
false | true |
1.1 * 1.1 == 1.21 |
false | true |
0.1 + 0.2 - 0.3 |
5.55e-17 | 0 |
floor((0.7 + 0.1) * 10) |
7 | 8 |
Exact large integers (#526). Whole numbers past 2^53 stay exact through arithmetic, powers, factorials and the integer functions.
| expression | 2.39 | 2.40 |
|---|---|---|
2^53 + 1 |
9,007,199,254,740,992 | 9,007,199,254,740,993 |
3^40 |
12,157,665,459,056,929,000 | 12,157,665,459,056,928,801 |
Honest edge values (#510). Special angles are exact, and a function outside its domain says so rather than answering NaN.
| expression | 2.39 | 2.40 |
|---|---|---|
sin(180 degrees) |
1.22e-16 | 0 |
log(-1) |
NaN | log(-1) has no real value: log is only defined for positive numbers. |
Number theory (#514): isprime, nextprime, modpow, modinv, factor(360) as 2^3 * 3^2 * 5, 10 choose 3 (120) and 5! (120).
Significant figures (#515): 1234567 to 3 sf is 1,230,000, and as engineering and as compact give the other two short forms.
Seeded randomness (#523): a random seed line, or the engine's random option, makes every roll, pick, shuffle and identifier the same on every run and every machine.
Units, money and data
Unit algebra (#513). Units multiply, divide, raise to powers and cancel the way numbers do, so an area is an area and a price per unit cancels its unit. A product no unit can hold is refused by name.
| expression | 2.39 | 2.40 |
|---|---|---|
5 m * 3 m |
15.00 m | 15.00 m² |
6 kWh * $0.30/kWh |
error: Undefined variable: kWh | $1.80 |
3 kg * $5/kg |
15.00 USD/kg | $15.00 |
1 / (2 m) |
0.50 m | 0.50 /m |
Cash flows (#519): npv of -1000, 300, 400, 500 at 10% is -21.04 and irr of -1000, 300, 400, 500 is 8.90%; payback of gives the period the flows repay in. The first flow is today.
Table lookups and bands (#507): column "cost" for "food" reads a table in the note by its row's label, and 45,000 through bands above applies a table of bands, such as a tax schedule, to an amount.
Pasted text (#520): total of numbers in "paid 12.50 and 7.25" is 19.75, and amounts in, match, matchcount and field read figures, patterns and JSON fields out of pasted text, on a matcher that cannot backtrack.
Live values (#512): every live value carries its provider and when it was fetched, through arithmetic, and a line ending frozen keeps its answer and the date it was fixed.
Solving, zones, places and distributions. solve(cos(x) = x, x) is 0.74, integral(x^2, x, 0, 3) is 9 and limit(sin(x)/x, x, 0) is 1 (#516). One time can be shown in several zones with the hours they share (#518), places are measured by latitude and longitude (#521), and the inverse normal, binomial, Poisson and Student's t distributions are available (#517).
For hosts
- Reference-aware editing (#524): find references, go to definition, hover, a rename that edits only real references, and
line Nreferences that stay on their lines when lines are inserted or deleted. - Did you mean (#509): an unknown name suggests the real names it is close to, and the suggestion is carried on the error for a one-click fix.
- Reading a line changes nothing (#559, #566, #567): highlighting, completion and
explainLinenever run a line's effects, and highlight spans are measured on the line as written. - The incremental evaluator agrees with a fresh pass (#555, #560, #565, #569), and every built-in package is exported from
solve-engine/packages(#556). - A result goes through
JSON.stringify(#598): a value with an exact decimal or a large integer writes its digits as a string, so a host can log, store or post it. - Renumbering moves what-if and sweep targets (#596), and
inputs of line Nlists what a section total and a table read (#597).
Answers that change
Each of these was a confident wrong answer in 2.39.
| expression | 2.39 | 2.40 |
|---|---|---|
"11:00 PM" + 2 |
13 | Text and a number cannot be added: + joins text only to other text. |
(1, 2) in miles |
0.00 miles | A bracketed list has no single amount to convert to miles. |
combination(5.5, 2) |
10 | combination counts whole things: 5.5 is not a whole number. |
10, total above, 5, total above |
25 on the last line | 15, since a subtotal is left out |
12.3 kg at $0.15/kg |
$1.84 | $1.85 |
-$5 |
$-5.00 | -$5.00 |
round(-2.5) |
-2 | -3, a half away from zero as to 0 dp already rounded |
(-8)^(1/3) |
NaN | -2 |
sin(1 m) |
0.84 | sin takes an angle or a plain number, not a length |
hypot(3 m, 400 cm) |
400.01 | 5.00 m |
ceil(-0.5) |
-0 | 0 |
9.81 m/s² |
error: Undefined variable: s² | 9.81 m/s² |
The fixes also cover multi-word units, unit powers and placement, tolerances written as a percentage, normalcdf and normalpdf with a mean and standard deviation, tan at a right angle, ranges that span a blank line or heading, text counted by the characters a reader sees, and highlighting. Each has its own before/now table in the changelog.
Known issues
The review before release found these, which this release does not fix. Each is being fixed for the next patch:
- A goal seek over a line that holds a sweep re-runs the whole sweep on every probe, which on the incremental path can take seconds and a large heap (#604). The value pool also keeps every value an incremental pass creates (#605), and
inputs of line Nwrites a very large value out in full before its size check (#606).parseDocumentis not affected. - A sweep that runs out of its budget names one step as the cause rather than the sweep as a whole (#607).
- A what-if written with
:nameadds the assignment instead of substituting it (#608); write the bare name,line 2 with price = 300, until then. - A one-character first line reads the rest of the document through
parseDocument(#609). - The ESM build throws "process is not defined" on import where Node's globals are missing, as in a browser tab or a module worker loaded without a bundler (#610). This was true of 2.39.0 as well; a bundler that defines
process.env.NODE_ENVis not affected.
Verification
npm run verify:cipasses: 11,890 tests across 544 suites,test:temporalin Europe/London, America/New_York and Pacific/Auckland, and the bundled-consumer contract (smoke:bundled,test:consumer).- All 1,393 documented examples are live, proven blocks that the build evaluates.
- Each feature shipped with an A/B of outputs against the previous build, with every difference classified as intended. The last of those A/B runs covered 34,709 cases for exact decimals.
- Before the release was cut, a cross-feature review of main ran a 40,400-case fuzz of every generator and about 150 lines through both document passes. It found three bugs where this release's features met (#581 to #583) and seven older gaps (#584 to #588, #590, #592), and a design review found five more in surfaces new to this release (#594 to #598), all fixed here.
- The benchmark gate passes, and the VM dispatch loop sits 14,542 bytes below V8's optimisation ceiling (#575).