Skip to content
JavaScript Basics: Validate Numbers from Text

JavaScript Basics: Validate Numbers from Text

October 16, 2026

A text field containing 12 still holds text. Before doing arithmetic, decide which inputs are acceptable, convert them, and check the result. This tutorial builds a finite-number reader and a stricter quantity reader.

All examples use built-in features and were tested on Linux with Node.js v24.19.0. Each file includes assertions; a failed assertion stops the program.

Prepare a scratch directory

Run this in Bash and keep the same terminal open:

You need Bash, a text editor, the mktemp, rm, and rmdir utilities, and Node.js on your PATH. Save source files as UTF-8; their .mjs extension selects ES modules.

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

Save the following files in this directory using your editor. No packages are required.

1. Convert text and require a finite number

Save this as finite-number.mjs:

import assert from 'node:assert/strict';

function readFiniteNumber(text) {
  if (typeof text !== 'string' || text.trim() === '') {
    throw new TypeError('Expected non-empty text');
  }
  const value = Number(text);
  if (!Number.isFinite(value)) {
    throw new RangeError('Expected a finite number');
  }
  return value;
}

assert.equal(readFiniteNumber(' 12.5 '), 12.5);
assert.equal(readFiniteNumber('0'), 0);
assert.equal(readFiniteNumber('1e3'), 1000);
assert.equal(readFiniteNumber('0x10'), 16);
assert.equal(Number(''), 0);
assert.equal(Number('   '), 0);
assert.equal(Number.isFinite('12'), false);

for (const input of ['', '  ', null, 12]) {
  assert.throws(() => readFiniteNumber(input), TypeError);
}
for (const input of ['12px', 'NaN', 'Infinity', '-Infinity', '1e309']) {
  assert.throws(() => readFiniteNumber(input), RangeError);
}

console.log(`Decimal: ${readFiniteNumber(' 12.5 ')}`);
console.log(`Exponent: ${readFiniteNumber('1e3')}`);
console.log(`Hexadecimal: ${readFiniteNumber('0x10')}`);
console.log('Invalid inputs rejected');

Run it:

node finite-number.mjs

Output:

Decimal: 12.5
Exponent: 1000
Hexadecimal: 16
Invalid inputs rejected

Number(text) converts the whole string. Invalid numeric text produces NaN. Empty or whitespace-only strings become zero, which is why the function checks for blank input first. The explicit type check also keeps values such as null out of this text-only interface.

Number.isFinite() accepts only finite numbers. It rejects NaN, either infinity, and strings without converting them. Convert first, then check.

This reader deliberately accepts exponent notation and hexadecimal text. If your input format only allows decimal digits, enforce that format too, as the next example does.

2. Validate a whole-number quantity

Save this as quantity.mjs:

import assert from 'node:assert/strict';

function readQuantity(text) {
  if (typeof text !== 'string') {
    throw new TypeError('Expected text');
  }
  const trimmed = text.trim();
  // One or more ASCII digits; no sign, decimal point, or exponent.
  if (!/^[0-9]+$/.test(trimmed)) {
    throw new RangeError('Expected decimal digits');
  }
  const value = Number(trimmed);
  if (!Number.isSafeInteger(value)) {
    throw new RangeError('Quantity is outside the safe integer range');
  }
  return value;
}

assert.equal(readQuantity(' 007 '), 7);
assert.equal(readQuantity('0'), 0);
assert.equal(readQuantity('9007199254740991'), Number.MAX_SAFE_INTEGER);
assert.equal(Number.isInteger(2.5), false);
assert.equal(Number.isInteger(2 ** 53), true);
assert.equal(Number.isSafeInteger(2 ** 53), false);
assert.throws(() => readQuantity(null), TypeError);

for (const input of ['', '-1', '+1', '2.5', '1e3', '0x10', '12px',
  '9007199254740992', '9007199254740993']) {
  assert.throws(() => readQuantity(input), RangeError);
}

console.log(`Quantity: ${readQuantity(' 007 ')}`);
console.log(`Largest safe quantity: ${readQuantity('9007199254740991')}`);
console.log(`2 ** 53 is an integer: ${Number.isInteger(2 ** 53)}`);
console.log(`2 ** 53 is safe: ${Number.isSafeInteger(2 ** 53)}`);

Run it:

node quantity.mjs

Output:

Quantity: 7
Largest safe quantity: 9007199254740991
2 ** 53 is an integer: true
2 ** 53 is safe: false

Number.isInteger() checks whether the stored number is integral. It cannot recover precision already lost during conversion.

Number.isSafeInteger() also requires the number to be between -(2 ** 53 - 1) and 2 ** 53 - 1, inclusive. Outside this range, different integers can become the same Number. Use safe-integer checks for counts that must remain exact, and consider BigInt for larger integers.

The regular expression checks the text format before conversion. This quantity rule allows zero and leading zeros, but rejects signs and decimal points. For a real order form, add its business limit, such as a maximum of 100 items, after these checks. Identifiers with meaningful leading zeros should generally stay strings.

3. Know when parseInt stops

Save this as parse-int-caveat.mjs:

import assert from 'node:assert/strict';

assert.equal(parseInt('12px', 10), 12);
assert.equal(parseInt('3.9', 10), 3);
assert.equal(parseInt('1e3', 10), 1);
assert.equal(Number.isNaN(Number('12px')), true);
assert.equal(Number('1e3'), 1000);

console.log(`parseInt('12px', 10): ${parseInt('12px', 10)}`);
console.log(`parseInt('3.9', 10): ${parseInt('3.9', 10)}`);
console.log(`parseInt('1e3', 10): ${parseInt('1e3', 10)}`);
console.log(`Number('1e3'): ${Number('1e3')}`);

Run it:

node parse-int-caveat.mjs

Output:

parseInt('12px', 10): 12
parseInt('3.9', 10): 3
parseInt('1e3', 10): 1
Number('1e3'): 1000

parseInt() reads an integer prefix and stops when it meets a character that cannot belong to that integer. The explicit radix 10 selects decimal parsing. This behavior is useful for intentional prefix extraction, but it does not validate that an entire input is an integer. For a quantity, use the text-format and safe-integer checks above.

Clean up

Run this in the same terminal to remove the three tutorial files and their empty scratch directory:

rm -- finite-number.mjs quantity.mjs parse-int-caveat.mjs
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

Acknowledgements

This post was created with assistance from AI.

Last updated on