Background
I'm writing user input validation for a client-side JavaScript web application. Users enter numerals representing an integer into an <input>
. As part of validation, the application must check that parseInt(input.value, 10)
will result in the integer represented by the user's input.
In JavaScript, all numbers are floating-points. Within the interval [-253, 253], all integers have exact representations. The challenge is to guarantee that the user has provided a number within the interval. parseInt()
doesn't work:
// Yields true
parseInt('9007199254740993', 10) === 9007199254740992;
I'm looking for any feedback. Firstly, correctness, then performance.
Current Solution
var isSafeIntegerString = function() {
'use strict';
// The digits of the greatest and least safe integers, 2**53 and
// -2**53 respectively.
var limDigits = [9, 0, 0, 7, 1, 9, 9, 2, 5, 4, 7, 4, 0, 9, 9, 2];
// The number digits in the safe integer limits.
var limLength = limDigits.length;
// The structure of safe integers.
//
// ^...$ The entire string must match the structure
//
// [-+]? May be preceded by either a '-' or a '+'
//
// 0* May have leading zeros
//
// (\d{1, limLength}) Must contains between 1 and limLength digits
var safeIntegerStructure = new RegExp('^[-+]?0*(\\d{1,' + limLength + '})$');
return function(str) {
// If str is not a string, it cannot be a safe integer string.
if (Object.prototype.toString.call(str) !== '[object String]') {
return false;
}
// If str doesn't have the structure of a safe integer string, it
// can't be one.
var match = str.match(safeIntegerStructure);
if (match === void 0) {
return false;
}
// str has a safe integer structure. The RE captured the
// significant digits (i.e., without the sign or leading zeros).
var numerals = match[1];
var numeralsLength = numerals.length;
// If str contains less significant digits than the limit, it is
// less and therefore is safe.
if (numeralsLength < limLength) {
return true;
}
// If str contains more significant digits than the limit, it is
// greater and therefore is not safe.
if (numeralsLength > limLength) {
return false;
}
// str and the limit contain the same number of digits. So, compare
// each digit, starting from the most significant.
for (var i = 0; i < numeralsLength; ++i) {
var digit = parseInt(numerals.charAt(i), 10);
var limDigit = limDigits[i];
if (digit < limDigit) {
return true;
}
if (digit > limDigit) {
return false;
}
}
// str is equal to a safe integer limit.
return true;
};
}();
Tests
(function() {
var test = function(str, isSafe) {
console.log(str, isSafe, isSafeIntegerString(str));
};
var schemata = [
[ '0', true ],
[ '-0', true ],
[ '1', true ],
[ '-1', true ],
[ '900719925474099', true ],
[ '900719925474099', true ],
[ '9007199254740991', true ],
[ '9007199254740991', true ],
[ '9007199254740992', true ],
[ '-9007199254740992', true ],
[ '09007199254740992', true ],
['-09007199254740992', true ],
[ '9007199254740993', false],
[ '-9007199254740993', false],
];
for (var i = 0; i < schemata.length; ++i) {
var schema = schemata[i];
test(schema[0], schema[1]);
}
}());