Why when rounding the sum of two numbers, the result is Nan?

Asked

Viewed 167 times

6

Why when rounding the sum of the two values below returns NaN?

numero1 = '1,10';
numero1 = parseFloat(numero1.replace(/[^0-9,]*/g, '').replace(',', '.')).toFixed(2); // output: 1.10
numero2 = '2,20';
numero2 = parseFloat(numero2.replace(/[^0-9,]*/g, '').replace(',', '.')).toFixed(2); // output: 2.20
total = Math.round(numero1 + numero2); // output: NaN
console.log(total);

  • No return Nan...

  • Pressing the result gives 3.

  • Probably some problem related to the environment in which you are running the code.

  • Edited, sorry, no code.

  • 1

    After converting the two numbers into string the operator + works as a concatenator.

3 answers

6

When you declare numero1 = '1,10', you specify that numero1 is originally a String, and therefore the result of parseFloat return a float briefly converted to String, which does not make the object capable of summing.

numero1 = '1,10';
numero1 = parseFloat(numero1.replace(/[^0-9,]*/g, '').replace(',', '.')).toFixed(2);

console.log(numero1);
console.log(typeof(numero1)); // string

So when you call Math.round(), you are concatenating the two strings, in which returns an invalid number, and therefore the result is a Not an Number (NaN).

The ideal is to call the Math.round(parseFloat(numero1) + parseFloat(numero2)) or create new variables to store the float without undergoing type mutation.

  • so before Math.round() converts the type to numeric it solves? the need for this is to add monetary value.

  • @Eliseub. I am not an expert on Javascript, either use. If "convert type" briefly change the object type of String for Float, otherwise.

  • 1

    Thank you, that’s right.

  • 1

    "parseFloat will return a float briefly converted to String" - that phrase got a little strange... Actually parseFloat returns a number, and then toFixed returns a string. It is not "briefly converted", what happened was we called a method (toFixed) returned a value of another type :-)

  • 1

    Thanks for the fix, @hkotsubo

5


As the other answers have said, the problem is because toFixed returns a string, then numero1 and numero2 will be strings. And when you use the operator + string, they are concatenated and the result is another string.

In case, after calls from toFixed, numero1 becomes the string '1.10' and numero2 becomes '2.20'. And when concatenating them, the result is the string '1.102.20':

let numero1 = '1,10';
numero1 = parseFloat(numero1.replace(/[^0-9,]*/g, '').replace(',', '.')).toFixed(2);
let numero2 = '2,20';
numero2 = parseFloat(numero2.replace(/[^0-9,]*/g, '').replace(',', '.')).toFixed(2);
console.log(numero1 + numero2); // 1.102.20

And this string, when passed to Math.round, results in NaN, since it is not a valid number (behavior described in the language specification). But just to be clear, contrary to what you said another answer, Math.round can yes receive strings as parameter, provided that they represent valid numbers:

console.log(Math.round('1.23')); // 1
console.log(Math.round('7.9')); // 8
console.log(Math.round('-1234')); // -1234
console.log(Math.round('3.02e4')); // 30200 (número em notação científica)
console.log(Math.round('0xff')); // 255 (número em hexadecimal)
console.log(Math.round('')); // 0 (OK, esse é um caso "estranho", string vazia não deveria ser um "número válido", mas o JS acha que é...)
console.log(Math.round('1.102.20')); // NaN
console.log(Math.round('123xyz')); // NaN


Round or round off, that is the question

That said, the correct solution depends on several factors. It has an important detail that should be taken into account: the rounding. toFixed, in addition to limiting the number of decimals, also round the value if necessary. Examples:

console.log(1.991.toFixed(2)); // '1.99'
console.log(2.999.toFixed(2)); // '3.00'

console.log(1.247.toFixed(2)); // '1.25'
console.log(1.244.toFixed(2)); // '1.24'

And in your case, it seems that you first want to round up the values (limiting them to 2 decimal places with toFixed) and then add them up - and in the end round up the result with Math.round. That is, if numero1 for '1,247' and numero2 for '1,245', the result will be 3:

function arredonda(n) {
    n = parseFloat(n.replace(/[^0-9,]*/g, '').replace(',', '.')).toFixed(2);
    // toFixed retorna uma string, então eu preciso converter para número de novo
    return parseFloat(n);
}

let numero1 = '1,247';
numero1 = arredonda(numero1);
let numero2 = '1,245';
numero2 = arredonda(numero2);
console.log(numero1); // 1.25
console.log(numero2); // 1.25
console.log(numero1 + numero2); // 2.5
console.log(Math.round(numero1 + numero2)); // 3

But what if I didn’t round up the numbers, and just apply Math.round in the final result? In that case the result would be 2:

function converte(n) {
    // não usa toFixed, retorna o número com todas as casas decimais
    return parseFloat(n.replace(/[^0-9,]*/g, '').replace(',', '.'));
}

let numero1 = '1,247';
numero1 = converte(numero1);
let numero2 = '1,245';
numero2 = converte(numero2);
console.log(numero1); // 1.247
console.log(numero2); // 1.245
console.log(numero1 + numero2); // 2.492
console.log(Math.round(numero1 + numero2)); // 2

Another option would be to take only the first two decimal places of the numbers (without rounding) and the result would also be 2:

function converte(n) {
    n = parseFloat(n.replace(/[^0-9,]*/g, '').replace(',', '.'));
    // em vez de toFixed, uso matemática para pegar somente as duas primeiras casas decimais
    return Math.floor(n * 100) / 100;
}
numero1 = '1,247';
numero1 = converte(numero1);
numero2 = '1,245';
numero2 = converte(numero2);
console.log(numero1); // 1.24
console.log(numero2); // 1.24
console.log(numero1 + numero2); // 2.48
console.log(Math.round(numero1 + numero2)); // 2

That is, it is up to you to decide which approach to use. It is unclear whether all the values you receive always have exactly 2 houses after the comma. If so, then there is no rounding to be done and you wouldn’t even need to use toFixed(2). Just use the second option above, which only converts to number, without rounding anything. But if the number of decimals varies and can be greater than 2, the choice of one of the above approaches can make all the difference in the final result.

In fact, the first replace It is unnecessary for these cases, as it removes everything that is neither number nor comma. But since the example strings only have numbers and a comma, then this replace does nothing. But anyway, if the real data have other characters, then it makes more sense to use it, otherwise it could remove it without problems.


Money?

For this comment, hints that you are working on monetary values. If that is indeed the case, then the best is do not use floating point numbers (read more on the subject here). Instead, you can simply remove the comma and work with the total amount of cents. Then, when it’s time to display the value, then you format it the way you think best.

In the case of monetary values, you can even use Intl.NumberFormat to format the value in a more "beautiful way":

function converteParaCentavos(n) {
    // assumindo que o valor sempre tem 2 casas depois da vírgula,
    //basta remover tudo que nao é número para ter a quantidade de centavos
    return parseFloat(n.replace(/[^0-9]*/g, ''));
}

// converte tudo para centavos
numero1 = '1,24';
numero1 = converteParaCentavos(numero1);
numero2 = '1,24';
numero2 = converteParaCentavos(numero2);
console.log(numero1); // 124
console.log(numero2); // 124
console.log(numero1 + numero2); // 248

// somente na hora de mostrar, eu divido por 100 para mostrar o valor em reais
console.log(Math.round((numero1 + numero2) / 100)); // 2

// exemplo com Intl.NumberFormat
let formatter = new Intl.NumberFormat('pt-BR', {
    style: 'currency',
    currency: 'BRL',
});

// sem arredondar
console.log(formatter.format((numero1 + numero2) / 100)); // R$ 2,48
// arredondando
console.log(formatter.format(Math.round((numero1 + numero2) / 100))); // R$ 2,00

  • 1

    as mentioned, yes I am working with monetary value, was clear your answer, gave everything right now, was a class all this and sanou other doubts that had, thank you for your wisdom and good will.

2

  • But if numero1 = '10.99' and numero2 = '21.01', the cents are "eaten";

  • I didn’t have this problem: https://repl.it/repls/RecursiveTenderOutcome

  • Dude, it was a hell of a coincidence to have a round value, but remaking the question, if it’s "1.11" and "1.13", the result would be "2.24", but it returns an integer, as in your example, and there’s no representation after the . (point).

  • 1

    @Eliseub. Returns an integer number because this is the return of Math.round. If you don’t want me to "eat" the pennies, don’t call Math.round

  • @Eliseub. Another thing, if you’re dealing with monetary values, it’s best not to use float, and also has to format always with the pennies. See in the final part of my answer :-)

Browser other questions tagged

You are not signed in. Login or sign up in order to post.