Dev.to WebDev ๐Ÿ›  Dev ๐Ÿ‘ 0 ๐Ÿ“– 3 min read

Calculators Should Show Their Work: Lessons From Building 91 Formula-First Tools in 6 Languages

Most online calculators are black boxes. You type in a number, you get a number back, and you have no idea whether the formula was right, which assumptions were baked in, or where it breaks down. When I built HexaCalc,

Most online calculators are black boxes. You type in a number, you get a number back, and you have no idea whether the formula was right, which assumptions were baked in, or where it breaks down.

When I built HexaCalc, a set of 91 free calculators in French, English, Spanish, Portuguese, German and Arabic, I set one rule: every result has to show how it was reached โ€” the formula, a worked example, the limits of the method and the sources. Here's what that rule forced me to get right.

1. Treat every formula as a pure, tested function

If the page promises "you can redo this by hand and get the same answer", the code has to be deterministic. Every calculation is a pure function with no hidden state, and it doesn't change without a test changing first.

// Percentage: (part รท total) ร— 100
export function percentage(part, total) {
  if (total === 0) return { ok: false, reason: "division_by_zero" };
  return { ok: true, value: (part / total) * 100 };
}
import { percentage } from "./percentage";

test("returns 25 for 5 of 20", () => {
  expect(percentage(5, 20)).toEqual({ ok: true, value: 25 });
});

test("refuses division by zero instead of returning Infinity", () => {
  expect(percentage(5, 0).ok).toBe(false);
});

Returning a structured "can't answer" result instead of NaN or Infinity is what lets the page explain why there's no result.

2. Edge cases are content, not just code

Division by zero, impossible triangles, rounding โ€” each is handled in code and explained on the page. Some examples that surprised users:

  • BMI doesn't measure body fat, and adult cut-offs don't apply to children.
  • A mortgage monthly payment is not an APR. Fees and insurance only appear if the form asks for them.
  • One-rep max (Epley) is an estimate from 1โ€“12 reps, not a real single-attempt max.

Writing the limits out made the tools more trustworthy, not less.

3. Convert temperatures through a pivot unit

Converting directly between every pair of units creates an explosion of formulas and rounding drift. Instead, temperature goes through kelvin:

const toKelvin = {
  C: (v) => v + 273.15,
  F: (v) => (v - 32) * 5 / 9 + 273.15,
  K: (v) => v,
};
const fromKelvin = {
  C: (k) => k - 273.15,
  F: (k) => (k - 273.15) * 9 / 5 + 32,
  K: (k) => k,
};

export const convertTemp = (v, from, to) => fromKelvin[to](toKelvin[from](v));

Two small tables instead of six pairwise formulas, and the scales stay consistent.

4. Do date maths in UTC calendar days

Duration calculators break twice a year thanks to daylight saving time: a "day" between two local midnights can be 23 or 25 hours. The fix is to treat dates as civil UTC dates:

const DAY = 86_400_000;
const utcDay = (y, m, d) => Date.UTC(y, m - 1, d) / DAY;

export const daysBetween = (a, b) =>
  utcDay(b.y, b.m, b.d) - utcDay(a.y, a.m, a.d);

Here, a "day" means a calendar date, not 24 astronomical hours โ€” and the page says so.

5. Localization is more than translating strings

Supporting six languages meant:

  • Number formatting via Intl.NumberFormat (1,234.5 vs 1 234,5 vs 1.234,5).
  • Right-to-left layout for Arabic, including making sure formulas and numbers still read correctly inside RTL text.
  • Translated URLs and locally relevant examples for each language, not just translated labels.
const fmt = (value, locale) =>
  new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }).format(value);

fmt(1234.5, "en"); // "1,234.5"
fmt(1234.5, "fr"); // "1 234,5"
fmt(1234.5, "de"); // "1.234,5"

6. Keep it all in the browser

Every calculation runs client-side. There's no account and nothing gets sent to a server, which keeps things simple and respects privacy โ€” especially for health or finance inputs people would rather not share.

Takeaways

  • Make your core logic pure and unit-tested so results are reproducible.
  • Return explicit "no answer" states and explain them to the user.
  • Use pivot units and UTC dates to avoid whole classes of bugs.
  • Treat localization as formatting, layout and examples โ€” not just strings.
  • Show the formula. People trust a number more when they can check it.

You can see the approach in action at hexacalc.com. If you've built calculators or unit converters, I'd love to hear which edge cases bit you.

๐Ÿ“ฐ Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes โ€” full credit and traffic to the original publisher.