Validate the Whole Number: A Small JavaScript Parser for Calculator Inputs
A statistics formula can be correct while its input parser quietly changes the dataset. Paste 12kg into a field, pass it to parseFloat(), and the result is 12. The suffix has disappeared. That is useful behavior for some parsers, but a poor default when a calculator promises to use the numbers a reader supplied.
This is a small, standalone implementation exercise from CalculatorGeek. It shows one explicit input contract and its limitations; it does not describe the production internals of our calculators.
Choose a format before choosing a conversion function
Suppose a textarea accepts one decimal number per line. We allow a sign, a decimal point, and scientific notation. We reject unit suffixes, thousands separators, hexadecimal input, and commas. Outer whitespace is ignored; an empty item between two numbers is an error.
Rejecting commas is deliberate. 1,5 could mean two observations or one decimal-comma value. Splitting on every punctuation character would silently make that decision for the reader. An internationalized application should offer an explicit format choice and a parser for that format. This example supports decimal points only.
There are three separate checks: does the input follow the grammar, does conversion produce a finite JavaScript number, and does the resulting value meet the calculation's domain rules? This parser handles the first two. A later calculation still decides whether negative values, zero, or a particular sample size make sense.
Validate first, convert second
export function parseDecimalLines(input) {
if (typeof input !== 'string') throw new TypeError('Expected text');
if (input.length > 20000) throw new RangeError('Input is too long');
const text = input.trim();
if (!text) throw new Error('Enter at least one number');
const rows = text.split(/\r\n|\n|\r/);
if (rows.length > 1000) throw new RangeError('At most 1000 values');
const decimal = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
return rows.map((row, index) => {
const token = row.trim();
if (!decimal.test(token)) {
throw new Error(`Item ${index + 1}: use one decimal per line`);
}
const value = Number(token);
if (!Number.isFinite(value)) {
throw new Error(`Item ${index + 1}: outside the finite range`);
}
return { token, value };
});
}
The anchored expression checks the entire trimmed token. The finite check then catches overflow such as 1e309. Keeping both the token and the converted value helps an interface show what the user entered alongside what the calculation will use.
The length and item limits are example product choices, not universal safe limits. They bound this function's work; they do not replace server-side request limits or validation elsewhere in an application.
Test the mistakes that would change an answer
Save the function as parser.mjs, then place this executable smoke check beside it as check.mjs:
import assert from 'node:assert/strict';
import { parseDecimalLines } from './parser.mjs';
const rows = parseDecimalLines('5\n5\n20');
assert.equal(rows.length, 3);
assert.equal(rows.reduce((sum, row) => sum + row.value, 0), 30);
for (const bad of ['12kg', '1,5', '0x10', '1e309', '1\n\n2']) {
assert.throws(() => parseDecimalLines(bad));
}
assert.deepEqual(
parseDecimalLines('+2.5\n.5\n-2e-3').map(row => row.value),
[2.5, 0.5, -0.002]
);
Run node check.mjs. These examples test accepted formats, rejected ambiguous input, and a count-and-sum check. For the three-value dataset, the hand-calculated mean is 30 / 3 = 10. When comparing a downstream calculation with a separate tool, check its input count and settings as well as its displayed result. CalculatorGeek's descriptive statistics calculator is one reference for exploring such a worked dataset; this article is published by CalculatorGeek.
A valid token is not an exact decimal
This code deliberately uses JavaScript Number. It does not preserve arbitrary decimal precision. For example, converting 9007199254740993 produces 9007199254740992, while the returned token still retains the entered digits. 1e-400 underflows to zero and passes the finite check. Negative zero also remains possible.
Those are reasons to choose a different numerical contract when exactness matters. An application can reject unsafe integer inputs, use a decimal representation, or impose a documented magnitude and precision range. The appropriate rule depends on the task; adding a regular expression cannot make binary floating-point arithmetic exact.
Display validation errors beside the input, retain the original text, and explain the accepted format before the user pastes a dataset. The practical result is that ambiguous input becomes a visible decision instead of an unnoticed change to the calculation.
