DEV Community

fei mai
fei mai

Posted on

Importing My Own Calculator's Math Module in Node Found Two UI Bugs and No Math Bugs

I could have reviewed this code by reading it. Instead I imported it and threw 67 inputs at it, which took about twenty minutes and turned up something reading would have missed.

The module is 264 lines of exact rational arithmetic over BigInt. It imports into Node with no DOM stub, no jsdom, nothing. That is the whole trick — pure functions deserve pure tests, and a calculator that keeps its maths separate from its buttons can be tested from a terminal.

The setup

import { parse, slopeFromPoints, slopeFromEquation,
         slopeFromRiseRun, proportionSolve } from './math.mjs';
Enter fullscreen mode Exit fullscreen mode

Then a table of inputs, and a second implementation in Python using Fraction to recompute every answer from scratch. If two independently written rational engines agree on 59 numbers, the numbers are probably right.

Sixty-seven inputs across five groups: 24 point pairs, 14 proportion setups with one blank cell, 17 raw strings straight into the parser, 6 Ax + By = C triples, 6 rise/run pairs.

Hostile by design. Negative coordinates. .5 and 5.. 3/-4 and -3/-4. Mixed numbers like 1 1/2. A delta of 1e-7. Equal x values. Identical points.

What it got right

Group Inputs Returned a number Rejected with a message
Two points 24 24 0
Proportion cells 14 13 1
Raw strings 17 12 5
Equation 6 5 1
Rise / run 6 5 1

Fifty-nine numbers, fifty-nine matches. Zero wrong.

The interesting ones:

5, 3, 5.0000001, 9   →  m = 60000000,  Δx = 1/10000000,  Δy = 6
0.1, 0.2, 0.3, 0.4   →  m = 1,  b = 1/10
1 1/2, 2, 3 1/2, 6   →  m = 2,  b = -1
2, 5, 2, 9           →  undefined slope, x = 2
4, 4, 4, 4           →  one point, not a line
Enter fullscreen mode Exit fullscreen mode

The first line is why exact fractions beat floats. A float pipeline gives you 59999999.99... and then rounds it into a confident lie. BigInt gives you the integer and keeps the run as a fraction.

Eight inputs were rejected, all with a sentence a student can use: a zero denominator, a mixed number whose fraction is ≥ 1, "Horizontal run cannot be zero — a vertical rise has no numerical grade." None of them silently returned 0, which is the failure that actually costs marks.

Bug one: the angle loses its sign

m = 2   →  grade 200%    angle 63.43°
m = -2  →  grade -200%   angle -63.43°?  no. 63.43°.
Enter fullscreen mode Exit fullscreen mode

The percentage grade keeps the sign. The angle does not. Two neighbouring cells in the same stat grid therefore describe opposite lines.

The code comment says this is deliberate — engineering asks "how steep", not "which way", so the acute angle is the answer. Reasonable. Except the label reads Angle to horizontal and the FAQ never says the sign is discarded, so a reader with a falling line and a protractor has to infer it from the cell next door disagreeing.

One word in a label, or an extra field. Either is fine. Silence is not.

Bug two: two different minus characters

y = -2x − 1

The first minus is U+002D, an ASCII hyphen, straight off the rational's toString(). The second is U+2212, typed by hand into the template. Nine of twenty-one equation strings in the test set carry the U+2212.

At body size in a serif they are almost indistinguishable. Almost — U+2212 is wider, centred on the maths axis, and the kerning around it is different. Put the output next to a printed textbook and the constant's minus drifts.

This is the kind of thing a unit test catches only if someone thought to assert on the string. Nobody did. Now it is written down.

What a terminal cannot tell you

The graph. drawGraph() wants a DOM node and pointer events, so I stubbed nothing and opened no browser. Whether the line is plotted where the numbers say, whether the axes really use one scale as the FAQ claims, what happens when you drag a point past the origin — all unknown, and all of it is the part people look at.

Same for mobile: no idea what the steps panel does at 375px.

Fifty-nine passing assertions describe the maths. They describe nothing about the page.

Our test harness failed before the code did

First run flagged two wrong answers. Both were our regex reading the slope is 12 ÷ 4 = 3 as a slope of 12.

Second run flagged a third. That one was our Python harness failing to parse 1 1/2 and reading it as 1. The calculator was right; the test was broken.

Three false alarms, all from the code doing the accusing. Worth remembering the next time a script confidently tells you your site is broken.

Disclosure

slopecalculatoronline.com is mine. That is why the module was one curl away and why the test took an evening rather than a week.

Three input modes, ten result fields: slope calculator.

Two cosmetic fixes and one untested graph. The maths is done; the last mile is a label, a hyphen, and a browser I have not opened yet.

Top comments (0)