30-seconds-of-code: Validate number: use only isFinite?

Created on 12 Dec 2017  路  19Comments  路  Source: 30-seconds/30-seconds-of-code

Validate number

Use !isNaN in combination with parseFloat() to check if the argument is a number. Use isFinite() to check if the number is finite.

const validateNumber = n => !isNaN(parseFloat(n)) && isFinite(n);
// validateNumber('10') -> true

According to the MDN documentation, isFinite() returns "false if the argument is positive or negative Infinity or NaN; otherwise, true".

isFinite(Infinity);  // false
isFinite(NaN);       // false
isFinite(-Infinity); // false

isFinite(0);         // true
isFinite(2e64);      // true
isFinite(910);       // true

isFinite(null);      // true, would've been false with the 
                     // more robust Number.isFinite(null)

isFinite('0');       // true, would've been false with the 
                     // more robust Number.isFinite("0")

So technically, the !isNan() isn't required and you could shorten your validateNumber:

const validateNumber = n => isFinite(n);
// validateNumber('10') -> true
help wanted question

Most helpful comment

I'm confused why this code would even use !isNaN(x) since NaN == false. That parsing isn't needed. Because 0 is a number. However to check if something is a number you need to do some coercion checking to make sure. If it's coerced form is equal to it's numerical form then it is a number. Really what we want are more test cases that would be considered valid or invalid:

Tests

validateNumber('10') // true
validateNumber(false) // false
validateNumber(true) // false
validateNumber(10) // true
validateNumber(0.10) // true
validateNumber(010) // true
validateNumber(0xA) // true
validateNumber(NaN) // false
validateNumber(null) // false
validateNumber(undefined) // false
validateNumber('10%') // false, % is a construct
validateNumber('10px') // false
validateNumber(Infinity) // false
validateNumber(Math.PI) // true, but is such an extreme edge case...
validateNumber(0) // true, will probably wreck things
validatenumber(1e10) // true

test based coding is really the only way to infallible say "This method is complete to the best of my knowledge". As new tests fail that should be valid we then know the code is invalid. Fix, iterate, evolve.

Possible Code

const validateNumber = n => !isNaN(parseFloat(n)) && isFinite(n) && Number(n) == n;

Tested Possible Code

Notice negating on false cases. I should expect an entire array of true
image

EDIT:
Test cases now include an exponential notation number ~ skatcat

All 19 comments

I think I based the snippet on this SO answer. It might be outdated or there might be something I don't understand. Does anyone know of any cases where Number.isFinite() alone would fail?

馃 what about?

const validateNumber = n => typeof n === 'number';

@fesebuv I believe the function is meant to allow a number string as well.

Also OP, what about booleans?

const validateNumber = n => isFinite(n)
validateNumber(true) // true

If it's meant to be an actual number, then Number.isFinite() should suffice. However, I thought the purpose was to also allow a string as input which makes it more useful when grabbing, for example, text from an input/textarea or an element attribute.

@atomiks @fesebuv @Chalarangelo

I think this would do:

const validateNumber = n => isFinite(parseFloat(n))

Yes, the function is meant to allow strings containing numerical data, such as '10' for example, to evaluate to true. @elderhsouza I think this should probably work as intended. Can anyone find any test cases that break this? Otherwise, we should get a PR going and change the current solution to this.

@elderhsouza

validateNumber('20px') // true

@atomiks technically speaking '20px' is a numeric value in CSS. 馃槢

@Chalarangelo @atomiks

I'm using this to test the cases, and to be fair I now think @Chalarangelo was right all along, the first option doesn't break with any test cases so far:

isNumber = n => !isNaN(parseFloat(n)) && isFinite(n)

test = [Infinity, NaN, -Infinity, 0, 2e64, 910, null, '0', undefined, true, '20px', 50, '0xFFFFFF', 0xFFFFFF, '0xfff']
results = test.map(isNumber)

console.table([test, results])

I told you guys where I think I found the original, but the clarification why it works is unclear on SO. Can anyone figure it out and explain it better so we can update the snippet's description?

isNumber([1]) returns true. Any object that can be coerced into a number will return true too

I think the only safe way to do it is to explicitly whitelist numbers & strings

isNumber = n => {
  const type = typeof n;
  if (type !== 'string' && type !== 'number') {
    return false;
  }

  return isFinite(n);
}

Edit 1: Beware of toPrimitive and toString too:

obj = {
  [Symbol.toPrimitive](hint) {
    if (hint === 'string') {
      return '10';
    }

    if (hint === 'number') {
      return 10;
    }


    return true;
  }
};

isNumber(obj); // true

obj = {
  toString() {
    return '10';
  }
};

isNumber(obj); // true

Edit 2: Forgot valueOf but it only is called when converting using Number, not parseFloat

@Ephys isNumber([1,2]) returns false, though. I mean an array with just one number sounds reasonably valid to me, as parseFloat([1]) will work properly. JS is weird when it comes to stuff like this.

@Chalarangelo That's due to coercion (the array is stringified into '1' then parsed as an int -- notice how parseFloat([1,2]) works too for the same reason) and that has caused more bugs than I can ever count. But if you want to consider arrays and above to be numbers it's up to you and likely depends on what you're trying to achieve :)

@Ephys you make a pretty good point. This can cause trouble, but limiting to specific data types can be troublesome as well. Generally the parsing system of JS is terrible, I mean parseFloat('123I am a little lamb') returns 123 for no apparent reason. I know why it works and I know this is the defined behavior, but I hate it and there are so many edge-cases that this whole thing becomes a nightmare to deal with.

In general, I agree, numbers and strings that can be converted to numbers should be considered valid by the implemented function (unless the are equal to Infinity), except that some strings that evaluate to numbers are not really numbers and we need to deal with those, I suppose.

@atomiks @elderhsouza
in that case:

const validateNumber = n => parseInt(n, 10) =>  typeof n === 'number';

@Chalarangelo I definitely agree, parsing numbers in javascript is a pain with so many corner cases I lost count (which is why I restrict to usually restrict to strings/numbers and use Number to parse instead of parseFloat).

By the way the reason the solution works for most cases is that you're using two different kind of number conversions that happen to exclude the wrong results of each other. If it helps I made a document comparing the possible parsing methods to try and figure out the mess: https://gist.github.com/Ephys/4d9731d5c7a945dcc752d279eaeaa08f

@Ephys that's a lot of effort you put in, but unfortunately I am too tired to read through the whole thing right now. First thing in the morning, though. I really want to figure out what to do with this specific snippet as I expect it to be used by a lot of people.

@fesebuv this still returns true for '20px', so it's not right yet.

I'm confused why this code would even use !isNaN(x) since NaN == false. That parsing isn't needed. Because 0 is a number. However to check if something is a number you need to do some coercion checking to make sure. If it's coerced form is equal to it's numerical form then it is a number. Really what we want are more test cases that would be considered valid or invalid:

Tests

validateNumber('10') // true
validateNumber(false) // false
validateNumber(true) // false
validateNumber(10) // true
validateNumber(0.10) // true
validateNumber(010) // true
validateNumber(0xA) // true
validateNumber(NaN) // false
validateNumber(null) // false
validateNumber(undefined) // false
validateNumber('10%') // false, % is a construct
validateNumber('10px') // false
validateNumber(Infinity) // false
validateNumber(Math.PI) // true, but is such an extreme edge case...
validateNumber(0) // true, will probably wreck things
validatenumber(1e10) // true

test based coding is really the only way to infallible say "This method is complete to the best of my knowledge". As new tests fail that should be valid we then know the code is invalid. Fix, iterate, evolve.

Possible Code

const validateNumber = n => !isNaN(parseFloat(n)) && isFinite(n) && Number(n) == n;

Tested Possible Code

Notice negating on false cases. I should expect an entire array of true
image

EDIT:
Test cases now include an exponential notation number ~ skatcat

Resolved in #101 as suggested. If any problems arise, comment below to reopen the issue.

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for any follow-up tasks.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fplgusmao picture fplgusmao  路  4Comments

ecwyne picture ecwyne  路  4Comments

Priyansh2001here picture Priyansh2001here  路  5Comments

fuchao2012 picture fuchao2012  路  4Comments

Speuta picture Speuta  路  3Comments