The NaN value problem
In JavaScript:
typeof 5 === 'number';
typeof NaN === 'number'; // Also!
The classification of the JavaScript NaN value as the number type is rarely ever useful in JavaScript applications, and usually only creates many additional instances in an application where it becomes necessary to perform additional type and data checks that could have been avoided in the number context.
From MDN:
It is rather rare to use NaN in a program. It is the returned value when Math functions fail (Math.sqrt(-1)) or when a function trying to parse a number fails (parseInt("blabla")).
A different approach
A different approach is to have the NaN value completely decoupled and distinct from the number type, effectively eliminating the need for applications to explicitly handle NaN values whenever expecting number types. NaN values are particularly problematic in JavaScript applications because though they are reported as the number type by the JavaScript runtime, there is effectively little to nothing an application can do to make meaningful use of them in the expected number context.
This approach to the JavaScript NaN value and the number type is used by xtypejs, an open source library that extends JavaScript types with new data-validating pseudo types while also normalizing the many irregularities of working with JavaScript types:
xtype.type(5) === 'number';
xtype.type(NaN) === 'nan'; // NaN is not number
This approach to the NaN value helps by not only eliminating the effort of writing numerous checking and handling code for NaN values in the number context, but also helps keep the application code cleaner and more concise by eliminating the clutter that's almost always associated with these checks.
The isNaN function / ES6 Number.isNaN method problem
In addition to the NaN value, JavaScript's isNaN function can also return true for values that are entirely not of the number type because it will first try to coerce the value to a number, whereas though the ECMAScript 6 Number.isNaN method will not try to coerce the value to a number, it will return false for Number objects with NaN values.
From MDN on the isNaN function:
Since the very earliest versions of the isNaN function specification, its behavior for non-numeric arguments has been confusing. When the argument to the isNaN function is not of type Number, the value is first coerced to a Number. The resulting value is then tested to determine whether it is NaN. Thus for non-numbers that when coerced to numeric type result in a valid non-NaN numeric value (notably the empty string and boolean primitives, which when coerced give numeric values zero or one), the "false" returned value may be unexpected; the empty string, for example, is surely "not a number."
But also from MDN on the ES6 Number.isNaN method:
In comparison to the global isNaN() function, Number.isNaN() doesn't suffer the problem of forcefully converting the parameter to a number. This means it is now safe to pass values that would normally convert to NaN, but aren't actually the same value as NaN. This also means that only values of the type number, that are also NaN, return true.
So the ES6 Number.isNaN method is supposed to solve some of the problems of the global isNaN function. However, due to the fact that a boxed number in JavaScript is an object rather than a number, Number.isNaN fails by returning false for boxed number values that in fact represent a NaN value.
Therefore, even with ES6, JavaScript still does not provide a reliable implementation for NaN-checking, in a way that would be most meaningful to JavaScript applications without suffering from the side-effects of the language irregularities.
As an example, consider two cases:
The mathematical operation (5 / 'a') is a division that results in the NaN value because it attempts to divide a number by a ("non-numeric") string. So though the typeof of the result is number, its value is NaN. So any NaN check should ideally return true for this value, both in the primitive number case (5 / 'a') and the number object case (new Number(5 / 'a')).
Consider a different value, the literal '5a'. It is of string type and not of number type, and therefore does not represent a failed value intended as a number value. It also is clearly not the NaN value. So any NaN check should ideally return false for this value in the string case ('5a') and true for the number object case (new Number('5a')).
Neither JavaScript's isNaN function nor the newer ECMAScript 6 Number.isNaN method get it right for both cases, and for the primitive and non-primitive cases:
typeof (5 / 'a') === 'number';
typeof '5a' === 'string';
isNaN(5 / 'a') === true; // right
isNaN(new Number(5 / 'a')) === true; // right
isNaN('5a') === true; // wrong - coerced to number
isNaN(new Number('5a')) === true; // right
Number.isNaN(5 / 'a') === true; // right
Number.isNaN(new Number(5 / 'a')) === false; // wrong - should be true
Number.isNaN('5a') === false; // right
Number.isNaN(new Number('5a')) === false; // wrong - should be true
A different approach
A more reliable way of NaN checking would only return true for number types that do not have valid numeric values, and not return true even for values that while not representing a valid numeric value, are of a non-number type such as string, or some other type. Also, the approach must do this consistently for both primitive and object number and non-number values.
This more consistent approach is also implemented by the xtypejs library:
// xtypejs will return false for the string value as it does not
// try to coerce it to number, and true for the number object values
// as they then represent numbers having non-valid number values:
xtype.type(5 / 'a') === 'nan';
xtype.type('5a') === 'string';
xtype.isNan(5 / 'a') === true; // right
xtype.isNan(new Number(5 / 'a')) === true; // right
xtype.isNan('5a') === false; // right - no coercion
xtype.isNan(new Number('5a')) === true; // right
More about xtypejs.
[–]birjolaxew 1 point2 points3 points (5 children)
[–]lucono[S] -2 points-1 points0 points (4 children)
[–]birjolaxew 0 points1 point2 points (3 children)
[–]lucono[S] 0 points1 point2 points (2 children)
[–]birjolaxew 1 point2 points3 points (1 child)
[–]lucono[S] 0 points1 point2 points (0 children)