parseInt vs unary plus, when to use which?
Asked Answered
B

6

213

What are the differences between this line:

var a = parseInt("1", 10); // a === 1

and this line

var a = +"1"; // a === 1

This jsperf test shows that the unary operator is much faster in the current chrome version, assuming it is for node.js!?

If I try to convert strings which are not numbers both return NaN:

var b = parseInt("test", 10); // b === NaN
var b = +"test"; // b === NaN

So when should I prefer using parseInt over the unary plus (especially in node.js)???

edit: and what's the difference to the double tilde operator ~~?

Blubbery answered 14/6, 2013 at 10:44 Comment(2)
Benchmark jsperf.com/parseint-vs-unary-operatorTransship
@RokoC.Buljan The service seems dead. Any update?Incapacious
A
218

Well, here are a few differences I know of:

  • An empty string "" evaluates to a 0, while parseInt evaluates it to NaN. IMO, a blank string should be a NaN.

      +'' === 0;              //true
      isNaN(parseInt('',10)); //true
    
  • The unary + acts more like parseFloat since it also accepts decimals.

    parseInt on the other hand stops parsing when it sees a non-numerical character, like the period that is intended to be a decimal point ..

      +'2.3' === 2.3;           //true
      parseInt('2.3',10) === 2; //true
    
  • parseInt and parseFloat parses and builds the string left to right. If they see an invalid character, it returns what has been parsed (if any) as a number, and NaN if none was parsed as a number.

    The unary + on the other hand will return NaN if the entire string is non-convertible to a number.

      parseInt('2a',10) === 2; //true
      parseFloat('2a') === 2;  //true
      isNaN(+'2a');            //true
    
  • As seen in the comment of @Alex K., parseInt and parseFloat will parse by character. This means hex and exponent notations will fail since the x and e are treated as non-numerical components (at least on base10).

    The unary + will convert them properly though.

      parseInt('2e3',10) === 2;  //true. This is supposed to be 2000
      +'2e3' === 2000;           //true. This one's correct.
    
      parseInt("0xf", 10) === 0; //true. This is supposed to be 15
      +'0xf' === 15;             //true. This one's correct.
    
Altar answered 14/6, 2013 at 10:46 Comment(6)
Also when using a radix +"0xf" != parseInt("0xf", 10)Seymour
i like your answer most so far, can you also explain what the difference to the double tilde operator ~~ is?Blubbery
@Blubbery That would be explained here. It's bitwise equivalent of Math.floor(), which basically chops off the decimal part.Altar
If you plan to use different radices then parseInt("0xf") would yield 15, too.Arana
Actually, "2e3" is not a valid integer representation for 2000. It is a valid floating point number though: parseFloat("2e3") will correctly yield 2000 as the answer. And "0xf" requires at least base 16, which is why parseInt("0xf", 10) returns 0, whereas parseInt("0xf", 16) returns the value of 15 you were expecting.Barnaby
@Altar the Dreamer and @hereandnow78: Double tilde cuts off the decimal part of the number, while Math.floor returns the closest lower number. They work the same for positive number, but Math.floor(-3.5) == -4 and ~~-3.5 == -3.Cantata
G
412

The ultimate whatever-to-number conversion table: Conversion table

EXPRS = [
    'parseInt(x)',
    'parseFloat(x)',
    'Number(x)',
    '+x',
    '~~x',
    'x>>>0',
    'isNaN(x)'

];

VALUES = [
    '"123"',
    '"+123"',
    '"-123"',
    '"123.45"',
    '"-123.45"',
    '"12e5"',
    '"12e-5"',
    
    '"0123"',
    '"0000123"',
    '"0b111"',
    '"0o10"',
    '"0xBABE"',
    
    '"4294967295"',
    '"123456789012345678"',
    '"12e999"',

    '""',
    '"123foo"',
    '"123.45foo"',
    '"  123   "',
    '"foo"',
    '"12e"',
    '"0b567"',
    '"0o999"',
    '"0xFUZZ"',

    '"+0"',
    '"-0"',
    '"Infinity"',
    '"+Infinity"',
    '"-Infinity"',
    'BigInt(1)',

    'null',
    'undefined',
    'true',
    'false',
    'Infinity',
    'NaN',

    '{}',
    '{valueOf: function(){return 42}}',
    '{toString: function(){return "56"}}',

];

//////

function wrap(tag, s) {
    if (s && s.join)
        s = s.join('');
    return '<' + tag + '>' + String(s) + '</' + tag + '>';
}

function table(head, rows) {
    return wrap('table', [
        wrap('thead', tr(head)),
        wrap('tbody', rows.map(tr))
    ]);
}

function tr(row) {
    return wrap('tr', row.map(function (s) {
        return wrap('td', s)
    }));
}

function val(n) {
    return n === true || Number.isNaN(n) || n === "Error" ? wrap('b', n) : String(n);
}

var rows = VALUES.map(function (v) {
    var x = eval('(' + v + ')');
    return [v].concat(EXPRS.map(function (e) {
        try {
            return val(eval(e));
        } catch {
            return val("Error");
        }
    }));
});

document.body.innerHTML = table(["x"].concat(EXPRS), rows);
table { border-collapse: collapse }
tr:nth-child(odd) { background: #fafafa }
td { border: 1px solid #e0e0e0; padding: 5px; font: 12px monospace }
td:not(:first-child) { text-align: right }
thead td { background: #3663AE; color: white }
b { color: red }
Gadwall answered 14/6, 2013 at 10:45 Comment(8)
Please add "NaN" to this table.Pimply
It might be worth adding an isNaN column to this table: for example, isNaN("") is false (i.e it's considered a number), but parseFloat("") is NaN, which can be a gotcha, if you're trying to use isNaN to validate the input before passing it to parseFloatCholecalciferol
You should also add '{valueOf: function(){return 42}, toString: function(){return "56"}}' to the list. The mixed results are interesting.Ziguard
So, the summary of the table is that + is just a shorter way of writing Number, and the furter ones are just crazy ways to do it that fail on edge cases?Monoxide
Is [].undef a thing, or is that just an arbitrary way of generating undefined? Can't find any record of "undef" related to JS through Google.Tremayne
@jcairney: yes, just a way to generate an undefinedGadwall
console.log(isNaN('0o10')); // true in IE. And IE does not support Number.isNaN(). Use isNaN() instead. The table in picture is not true by different browsers.Slang
Reason I will never use parseInt: parseInt("1abcde344543*&$$#!$") // 1Bearberry
A
218

Well, here are a few differences I know of:

  • An empty string "" evaluates to a 0, while parseInt evaluates it to NaN. IMO, a blank string should be a NaN.

      +'' === 0;              //true
      isNaN(parseInt('',10)); //true
    
  • The unary + acts more like parseFloat since it also accepts decimals.

    parseInt on the other hand stops parsing when it sees a non-numerical character, like the period that is intended to be a decimal point ..

      +'2.3' === 2.3;           //true
      parseInt('2.3',10) === 2; //true
    
  • parseInt and parseFloat parses and builds the string left to right. If they see an invalid character, it returns what has been parsed (if any) as a number, and NaN if none was parsed as a number.

    The unary + on the other hand will return NaN if the entire string is non-convertible to a number.

      parseInt('2a',10) === 2; //true
      parseFloat('2a') === 2;  //true
      isNaN(+'2a');            //true
    
  • As seen in the comment of @Alex K., parseInt and parseFloat will parse by character. This means hex and exponent notations will fail since the x and e are treated as non-numerical components (at least on base10).

    The unary + will convert them properly though.

      parseInt('2e3',10) === 2;  //true. This is supposed to be 2000
      +'2e3' === 2000;           //true. This one's correct.
    
      parseInt("0xf", 10) === 0; //true. This is supposed to be 15
      +'0xf' === 15;             //true. This one's correct.
    
Altar answered 14/6, 2013 at 10:46 Comment(6)
Also when using a radix +"0xf" != parseInt("0xf", 10)Seymour
i like your answer most so far, can you also explain what the difference to the double tilde operator ~~ is?Blubbery
@Blubbery That would be explained here. It's bitwise equivalent of Math.floor(), which basically chops off the decimal part.Altar
If you plan to use different radices then parseInt("0xf") would yield 15, too.Arana
Actually, "2e3" is not a valid integer representation for 2000. It is a valid floating point number though: parseFloat("2e3") will correctly yield 2000 as the answer. And "0xf" requires at least base 16, which is why parseInt("0xf", 10) returns 0, whereas parseInt("0xf", 16) returns the value of 15 you were expecting.Barnaby
@Altar the Dreamer and @hereandnow78: Double tilde cuts off the decimal part of the number, while Math.floor returns the closest lower number. They work the same for positive number, but Math.floor(-3.5) == -4 and ~~-3.5 == -3.Cantata
T
12

The table in thg435's answer I believe is comprehensive, however we can summarize with the following patterns:

  • Unary plus does not treat all falsy values the same, but they all come out falsy.
  • Unary plus sends true to 1, but "true" to NaN.
  • On the other hand, parseInt is more liberal for strings that are not pure digits. parseInt('123abc') === 123, whereas + reports NaN.
  • Number will accept valid decimal numbers, whereas parseInt merely drops everything past the decimal. Thus parseInt mimics C behavior, but is perhaps not ideal for evaluating user input.
  • Both trim whitespace in strings.
  • parseInt, being a badly designed parser, accepts octal and hexadecimal input. Unary plus only takes hexademical.

Falsy values convert to Number following what would make sense in C: null and false are both zero. "" going to 0 doesn't quite follow this convention but makes enough sense to me.

Therefore I think if you are validating user input, unary plus has correct behavior for everything except it accepts decimals (but in my real life cases I'm more interested in catching email input instead of userId, value omitted entirely, etc.), whereas parseInt is too liberal.

Tulipwood answered 27/6, 2013 at 17:39 Comment(1)
"Unary plus only takes hexadecimal" Don't you mean decimal?Multitudinous
S
1

Be carefull, parseInt is faster than + unary operator in Node.JS, it's false that + or |0 are faster, them are faster only for NaN elements.

Check this out:

var arg=process.argv[2];

rpt=20000;
mrc=1000;

a=[];
b=1024*1024*1024*1024;
for (var i=0;i<rpt;i++)
 a[i]=Math.floor(Math.random()*b)+' ';

t0=Date.now();
if ((arg==1)||(arg===undefined))
 for (var j=0;j<mrc;j++) for (var i=0;i<rpt;i++) {
  c=a[i]-0;
 }
t1=Date.now();
if ((arg==2)||(arg===undefined)) {
 for (var j=0;j<mrc;j++) for (var i=0;i<rpt;i++) {
  d=a[i]|0;
 }
}
t2=Date.now();
if ((arg==3)||(arg===undefined)) {
 for (var j=0;j<mrc;j++) for (var i=0;i<rpt;i++) {
  e=parseInt(a[i]);
 }
}
t3=Date.now();
 if ((arg==3)||(arg===undefined)) {
 for (var j=0;j<mrc;j++) for (var i=0;i<rpt;i++) {
  f=+a[i];
 }
}
t4=Date.now();

console.log(a[i-1],c,d,e,f);
console.log('Eseguiti: '+rpt*mrc+' cicli');
console.log('parseInt '+(t3-t2));
console.log('|0 '+(t2-t1));
console.log('-0 '+(t1-t0));
console.log('+ '+(t4-t3));
Sverige answered 11/3, 2015 at 18:43 Comment(0)
T
1

I recommend use Math.floor (or ~~ if you know numbers are positive) instead of parseString. +(expression) is out of scope, because +(expression) is more like parseFloat. Look this little benchmark:

// 1000000 iterations each one
node test_speed
Testing ~~, time: 5 ms
Testing parseInt with number, time: 25 ms
Testing parseInt with string, time: 386 ms
Testing Math.floor, time: 18 ms

The source code of benchmark:


/* el propósito de este script es evaluar
que expresiones se ejecutan más rápido para así 
decidir cuál usar */

main()
async function main(){
    let time, x 
    let number = 23456.23457
    
    let test1 = ()=>{
        x = 0
        time = Date.now() 
        for(let i=0;i<1000000;i++){
            let op = Math.floor(number / 3600)
            x = op
        }
        console.info("Testing Math.floor, time:", Date.now() - time, "ms")
    }

    let test2 = ()=>{
        x = 0
        time = Date.now() 
        for(let i=0;i<1000000;i++){
            let op = parseInt(number / 3600)
            x = op
        }
        console.info("Testing parseInt with number, time:", Date.now() - time, "ms")
    }

    let test3 = ()=>{
        x = 0
        time = Date.now() 
        for(let i=0;i<1000000;i++){
            let op = parseInt((number / 3600).toString())
            x = op
        }
        console.info("Testing parseInt with string, time:", Date.now() - time, "ms")
    }

    let test4 = ()=>{
        x = 0
        time = Date.now() 
        for(let i=0;i<1000000;i++){
            let op = ~~(number / 3600)
            x = op
        }
        console.info("Testing ~~, time:", Date.now() - time, "ms")
    }
    
    test4()
    test2()
    test3()
    test1()
    
}
Thirteen answered 5/1, 2021 at 22:33 Comment(0)
A
-2

Consider performance too. I was suprised that parseInt beats unary plus on iOS :) This is helpful for web apps with heavy CPU consumption only. As a rule-of-thumb I'd suggest JS opt-guys to consider any JS operator over another one from the mobile performance point of view nowadays.

So, go mobile-first ;)

Ario answered 19/7, 2013 at 14:8 Comment(6)
As the other posts explain they do quite different things, so you can't easily swap one for the other…Arana
@Bergi, right, but they also have a lot in common. Tell me just one performance solution in JavaScript that is definitely the only right choice? In general that's why rule-of-thumbs are out there for us. The rest is task-specific.Ario
@ArmanMcHitaryan this is useless microoptimisation and it does not worth it. Check out this article - fabien.potencier.org/article/8/…Mantelletta
@webvitaly, nice article. There are always very perf-oriented guys out there who just like to write "the fastest possible" code and in some specific projects that's not bad. That's why I mentioned "JS opt-guys to consider". this is not A MUST of course :), but I myself find it much more readable in addition.Ario
Do you have a citation for this? Your link is broken.Tulipwood
I can tell you guys that micro-optimizations like this made my 3D biomedical imaging code run at acceptable performance, so I wouldn't dismiss this stuff out of hand. I also saw changing Math.trunc to ~~ make a procedurally generated canvas render run acceptably smooth so YMMV. The reason that these optimizations worked was that it was in a tight loop being called millions of times per a frame.Festival

© 2022 - 2024 — McMap. All rights reserved.