|
The accepted answer failed your test #7 and I guess it's because you changed your mind. So this is a response to the accepted answer, with which I had issues.
During some projects I have needed to validate some data and be as certain as possible that it is a javascript numerical value that can be used in mathematical operations.
jQuery, and some other javascript libraries already include such a function, usually called isNumeric. There is also a post on stackoverflow that has been widely accepted as the answer, the same general routine that the afore mentioned libraries are using.
function isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
First, the code above would return true if the argument was an array of length 1, and that single element was of a type deemed as numeric by the above logic. In my opinion, if it's an array then its not numeric.
To alleviate this problem, I added a check to discount arrays from the logic
function isNumber(n) {
return Object.prototype.toString.call(n) !== '[object Array]' &&!isNaN(parseFloat(n)) && isFinite(n);
}
Of course, you could also use Array.isArray , jquery $.isArray or prototype Object.isArray instead of Object.prototype.toString.call(n) !== '[object Array]'
My second issue was that Negative Hexadecimal integer literal strings ("-0xA" -> -10) were not being counted as numeric. However, Positive Hexadecimal integer literal strings ("0xA" -> 10) were treated as numeric.
I needed both to be valid numeric.
I then modified the logic to take this into account.
function isNumber(n) {
return Object.prototype.toString.call(n) !== '[object Array]' &&!isNaN(parseFloat(n)) && isFinite(n.toString().replace(/^-/, ''));
}
If you are worried about the creation of the regex each time the function is called then you could rewrite it within a closure, something like this
var isNumber = (function () {
var rx = /^-/;
return function (n) {
return Object.prototype.toString.call(n) !== '[object Array]' &&!isNaN(parseFloat(n)) && isFinite(n.toString().replace(rx, ''));
};
}());
I then took CMSs +30 test cases and cloned the testing on jsfiddle added my extra test cases and my above described solution.
It may not replace the widely accepted/used answer but if this is more of what you are expecting as results from your isNumeric function then hopefully this will be of some help.
EDIT: As pointed out by Bergi, there are other possible objects that could be considered numeric and it would be better to whitelist than blacklist. With this in mind I would add to the criteria.
I want my isNumeric function to consider only Numbers or Strings
With this in mind, it would be better to use
function isNumber(n) {
return (Object.prototype.toString.call(n) === '[object Number]' || Object.prototype.toString.call(n) === '[object String]') &&!isNaN(parseFloat(n)) && isFinite(n.toString().replace(/^-/, ''));
}
Test the solutions
var testHelper = function() {
var testSuite = function() {
test("Integer Literals", function() {
ok(isNumber("-10"), "Negative integer string");
ok(isNumber("0"), "Zero string");
ok(isNumber("5"), "Positive integer string");
ok(isNumber(-16), "Negative integer number");
ok(isNumber(0), "Zero integer number");
ok(isNumber(32), "Positive integer number");
ok(isNumber("040"), "Octal integer literal string");
ok(isNumber(0144), "Octal integer literal");
ok(isNumber("-040"), "Negative Octal integer literal string");
ok(isNumber(-0144), "Negative Octal integer literal");
ok(isNumber("0xFF"), "Hexadecimal integer literal string");
ok(isNumber(0xFFF), "Hexadecimal integer literal");
ok(isNumber("-0xFF"), "Negative Hexadecimal integer literal string");
ok(isNumber(-0xFFF), "Negative Hexadecimal integer literal");
});
test("Foating-Point Literals", function() {
ok(isNumber("-1.6"), "Negative floating point string");
ok(isNumber("4.536"), "Positive floating point string");
ok(isNumber(-2.6), "Negative floating point number");
ok(isNumber(3.1415), "Positive floating point number");
ok(isNumber(8e5), "Exponential notation");
ok(isNumber("123e-2"), "Exponential notation string");
});
test("Non-Numeric values", function() {
equals(isNumber(""), false, "Empty string");
equals(isNumber(" "), false, "Whitespace characters string");
equals(isNumber("\t\t"), false, "Tab characters string");
equals(isNumber("abcdefghijklm1234567890"), false, "Alphanumeric character string");
equals(isNumber("xabcdefx"), false, "Non-numeric character string");
equals(isNumber(true), false, "Boolean true literal");
equals(isNumber(false), false, "Boolean false literal");
equals(isNumber("bcfed5.2"), false, "Number with preceding non-numeric characters");
equals(isNumber("7.2acdgs"), false, "Number with trailling non-numeric characters");
equals(isNumber(undefined), false, "Undefined value");
equals(isNumber(null), false, "Null value");
equals(isNumber(NaN), false, "NaN value");
equals(isNumber(Infinity), false, "Infinity primitive");
equals(isNumber(Number.POSITIVE_INFINITY), false, "Positive Infinity");
equals(isNumber(Number.NEGATIVE_INFINITY), false, "Negative Infinity");
equals(isNumber(new Date(2009, 1, 1)), false, "Date object");
equals(isNumber(new Object()), false, "Empty object");
equals(isNumber(function() {}), false, "Instance of a function");
equals(isNumber([]), false, "Empty Array");
equals(isNumber(["-10"]), false, "Array Negative integer string");
equals(isNumber(["0"]), false, "Array Zero string");
equals(isNumber(["5"]), false, "Array Positive integer string");
equals(isNumber([-16]), false, "Array Negative integer number");
equals(isNumber([0]), false, "Array Zero integer number");
equals(isNumber([32]), false, "Array Positive integer number");
equals(isNumber(["040"]), false, "Array Octal integer literal string");
equals(isNumber([0144]), false, "Array Octal integer literal");
equals(isNumber(["-040"]), false, "Array Negative Octal integer literal string");
equals(isNumber([-0144]), false, "Array Negative Octal integer literal");
equals(isNumber(["0xFF"]), false, "Array Hexadecimal integer literal string");
equals(isNumber([0xFFF]), false, "Array Hexadecimal integer literal");
equals(isNumber(["-0xFF"]), false, "Array Negative Hexadecimal integer literal string");
equals(isNumber([-0xFFF]), false, "Array Negative Hexadecimal integer literal");
equals(isNumber([1, 2]), false, "Array with more than 1 Positive interger number");
equals(isNumber([-1, -2]), false, "Array with more than 1 Negative interger number");
});
}
var functionsToTest = [
function(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
},
function(n) {
return !isNaN(n) && !isNaN(parseFloat(n));
},
function(n) {
return !isNaN((n));
},
function(n) {
return !isNaN(parseFloat(n));
},
function(n) {
return typeof(n) != "boolean" && !isNaN(n);
},
function(n) {
return parseFloat(n) === Number(n);
},
function(n) {
return parseInt(n) === Number(n);
},
function(n) {
return !isNaN(Number(String(n)));
},
function(n) {
return !isNaN(+('' + n));
},
function(n) {
return (+n) == n;
},
function(n) {
return n && /^-?\d+(\.\d+)?$/.test(n + '');
},
function(n) {
return isFinite(Number(String(n)));
},
function(n) {
return isFinite(String(n));
},
function(n) {
return !isNaN(n) && !isNaN(parseFloat(n)) && isFinite(n);
},
function(n) {
return parseFloat(n) == n;
},
function(n) {
return (n - 0) == n && n.length > 0;
},
function(n) {
return typeof n === 'number' && isFinite(n);
},
function(n) {
return !Array.isArray(n) && !isNaN(parseFloat(n)) && isFinite(n.toString().replace(/^-/, ''));
}
];
// Examines the functionsToTest array, extracts the return statement of each function
// and fills the toTest select element.
var fillToTestSelect = function() {
for (var i = 0; i < functionsToTest.length; i++) {
var f = functionsToTest[i].toString();
var option = /[\s\S]*return ([\s\S]*);/.exec(f)[1];
$("#toTest").append('<option value="' + i + '">' + (i + 1) + '. ' + option + '</option>');
}
}
var performTest = function(functionNumber) {
reset(); // Reset previous test
$("#tests").html(""); //Clean test results
isNumber = functionsToTest[functionNumber]; // Override the isNumber global function with the one to test
testSuite(); // Run the test
// Get test results
var totalFail = 0;
var totalPass = 0;
$("b.fail").each(function() {
totalFail += Number($(this).html());
});
$("b.pass").each(function() {
totalPass += Number($(this).html());
});
$("#testresult").html(totalFail + " of " + (totalFail + totalPass) + " test failed.");
$("#banner").attr("class", "").addClass(totalFail > 0 ? "fail" : "pass");
}
return {
performTest: performTest,
fillToTestSelect: fillToTestSelect,
testSuite: testSuite
};
}();
$(document).ready(function() {
testHelper.fillToTestSelect();
testHelper.performTest(0);
$("#toTest").change(function() {
testHelper.performTest($(this).children(":selected").val());
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script src="http://dl.getdropbox.com/u/35146/js/tests/testrunner.js" type="text/javascript"></script>
<link href="http://dl.getdropbox.com/u/35146/js/tests/testsuite.css" rel="stylesheet" type="text/css">
<h1>isNumber Test Cases</h1>
<h2 id="banner" class="pass"></h2>
<h2 id="userAgent">Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11</h2>
<div id="currentFunction"></div>
<div id="selectFunction">
<label for="toTest" style="font-weight:bold; font-size:Large;">Select function to test:</label>
<select id="toTest" name="toTest">
<option value="0">
1. !isNaN(parseFloat(n)) && isFinite(n)
</option>
<option value="1">
2. !isNaN(n) && !isNaN(parseFloat(n))
</option>
<option value="2">
3. !isNaN((n))
</option>
<option value="3">
4. !isNaN(parseFloat(n))
</option>
<option value="4">
5. typeof(n) != "boolean" && !isNaN(n)
</option>
<option value="5">
6. parseFloat(n) === Number(n)
</option>
<option value="6">
7. parseInt(n) === Number(n)
</option>
<option value="7">
8. !isNaN(Number(String(n)))
</option>
<option value="8">
9. !isNaN(+('' + n))
</option>
<option value="9">
10. (+n) == n
</option>
<option value="10">
11. n && /^-?\d+(\.\d+)?$/.test(n + '')
</option>
<option value="11">
12. isFinite(Number(String(n)))
</option>
<option value="12">
13. isFinite(String(n))
</option>
<option value="13">
14. !isNaN(n) && !isNaN(parseFloat(n)) && isFinite(n)
</option>
<option value="14">
15. parseFloat(n) == n
</option>
<option value="15">
16. (n - 0) == n && n.length > 0
</option>
<option value="16">
17. typeof n === 'number' && isFinite(n)
</option>
<option value="17">
18. !Array.isArray(n) && !isNaN(parseFloat(n)) && isFinite(n.toString().replace(/^-/, ''))
</option>
</select>
</div>
<div id="testCode"></div>
<ol id="tests">
<li class="pass">
<strong>Integer Literals <b style="color:black;">(0, 10, 10)</b></strong>
<ol style="display: none;">
<li class="pass">Negative integer string</li>
<li class="pass">Zero string</li>
<li class="pass">Positive integer string</li>
<li class="pass">Negative integer number</li>
<li class="pass">Zero integer number</li>
<li class="pass">Positive integer number</li>
<li class="pass">Octal integer literal string</li>
<li class="pass">Octal integer literal</li>
<li class="pass">Hexadecimal integer literal string</li>
<li class="pass">Hexadecimal integer literal</li>
</ol>
</li>
<li class="pass">
<strong>Foating-Point Literals <b style="color:black;">(0, 6, 6)</b></strong>
<ol style="display: none;">
<li class="pass">Negative floating point string</li>
<li class="pass">Positive floating point string</li>
<li class="pass">Negative floating point number</li>
<li class="pass">Positive floating point number</li>
<li class="pass">Exponential notation</li>
<li class="pass">Exponential notation string</li>
</ol>
</li>
<li class="pass">
<strong>Non-Numeric values <b style="color:black;">(0, 18, 18)</b></strong>
<ol style="display: none;">
<li class="pass">Empty string: false</li>
<li class="pass">Whitespace characters string: false</li>
<li class="pass">Tab characters string: false</li>
<li class="pass">Alphanumeric character string: false</li>
<li class="pass">Non-numeric character string: false</li>
<li class="pass">Boolean true literal: false</li>
<li class="pass">Boolean false literal: false</li>
<li class="pass">Number with preceding non-numeric characters: false</li>
<li class="pass">Number with trailling non-numeric characters: false</li>
<li class="pass">Undefined value: false</li>
<li class="pass">Null value: false</li>
<li class="pass">NaN value: false</li>
<li class="pass">Infinity primitive: false</li>
<li class="pass">Positive Infinity: false</li>
<li class="pass">Negative Infinity: false</li>
<li class="pass">Date object: false</li>
<li class="pass">Empty object: false</li>
<li class="pass">Instance of a function: false</li>
</ol>
</li>
</ol>
<div id="main">
This page contains tests for a set of isNumber functions. To see them, take a look at the source.
</div>
<div>
<p class="result">Tests completed in 0 milliseconds.
<br>0 tests of 0 failed.</p>
</div>
|
jQuery.isNumeric
utility function: api.jquery.com/jQuery.isNumeric – Ates Goral Nov 16 '11 at 20:04jQuery.isNumeric
will fail the OP's seventh test case (IsNumeric('0x89f') => *false*
). I'm not sure if I agree with this test case, however. – Tim Lehner Aug 27 '12 at 16:42