Compute the rest of a decimal division in Javascript

Asked

Viewed 22,035 times

3

I’m working with values (decimal(18,2)) a sale where the sum of the price of the products is to be converted into a number of parcels. So that I can divide the total of the products exactly for the plots, I also need to calculate the leftover division and then apply this leftover to a single final plot. For that I do:

var parcelas = 3;
var produtos = [
    {nome: 'bola', valor: 10},
    {nome: 'pipa', valor: 5.3},
    {nome: 'carro', valor: 15}
]; 

//total dos produtos (resultado = 30.3 ~> R$30,30)
var total = 0;
for(var i in produtos){
  total = total + produtos[i].valor;
}

//verifico se há resto na divisão
var restoDivisao = total % parcelas

When I check if there’s the rest in the division, it’s returning 0.30 but if I share 30.3 / 3 the result is 10.1.

What is the correct way for me to check if there is rest in a division with decimal values?

Doing tests, somehow I managed to do so:

var restoDivisao = (((total) * 100) % parcelas) / 100;

This way works but I can’t accept that there really isn’t a more "clean" method than this. There is a more visually correct way to get to the rest of this division?

6 answers

8

You just figured out what every programmer should know before making a code that deals with money. The "decimal" values are actually binary and are not exact. For much of the things that need unbroken values this inaccuracy does not matter much, with money and some other types of data matters. Javascript by default works with the binary value.

Maybe there’s no error in the algorithm but I won’t say the operation is right because I haven’t seen all the code that distributes the parcels. Unless it’s just this. Then I don’t understand this as plot calculation and there’s a very wrong logic.

You can do whatever you want with binary data that you won’t solve. Probably your entire system has these problems and possibly causing financial errors. And it’s no use just solving this calculation. Will solve at this point and continue causing problem in the rest of the system. Never believe in solutions that "work", follow right solutions. What works can stop working because it wasn’t right. Always learn the right one.

This is the greatest danger in this type of problem. It is very easy to find that solved the problem and it continue to exist.

The solution is to change the way you store and manipulate all this data. Some languages have a type that already helps in this, others do not. As is the case with JS. But this is a "problem" of all languages.

I will not go into detail because it has already been answered in: How to represent money in Javascript?. There is the solution using only integers or libraries that abstract a little its use, but does not solve all situations.

See also What is the correct way to use the float, double and decimal types?.

4


This simple function solves the problem

function getValorParcelas(precoTotal,numeroParcelas){
    var valorParcela            = parseFloat(precoTotal/numeroParcelas).toFixed(2);
    var valorPrimeiraParcela    = precoTotal-(valorParcela*(numeroParcelas-1));

    return { 'valor_primeira_parcela': valorPrimeiraParcela , 'valor_parcela': valorParcela, 'numero_parcelas': numeroParcelas };
}

To recover the data:

var parcelaObj = getValorParcelas(100,3);
alert(parcelaObj.valor_primeira_parcela);
alert(parcelaObj.valor_parcela);
alert(parcelaObj.numero_parcelas);

3

I’ve read all the answers and I haven’t seen one sticking to the root of your problem. Your problem is not really in Javascript (or programming), but in the mathematical logic behind the operation Módulo.

To better understand, let’s go to the most basic possible model of the division:

inserir a descrição da imagem aqui

See that the first operation is 30 30, as a result 1 and rest 3. When you get the rest 0.3, are those 0.3 you are getting. The answer is not 0.1, for 0.1 is the decimal value of the division, not the rest.

Let’s look at the code you say works:

var restoDivisao = (((total) * 100) % parcelas) / 100;

In this case you are taking decimal values up to 2nd precision and making them whole (as the 2nd grade division suggests you do) and then calculating the rest.

((100)*100) MOD 7) / 100 = 0,04

See that 14,28 * 7 = 99,96, 0.04 being the remaining missing to interact the R$100,00


Analyzing a case where the plots are larger than the total value:

((8,80)*100) MOD 12) / 100 = 0,04

See that 0,73 * 12 = 8,76 missing only 0,04.

Summarizing and calculating plot values

// Altere esses valores para cada compra específica
var numeroDeParcelas = 12;
var valorTotal = 8.80;
// ------------------------------------------------

// Use esta formula para determinar os resultados
var restoDivisao = (((valorTotal) * 100) % numeroDeParcelas) / 100; // 0.040000011
var cadaParcela = Math.floor((total * 100) / parcela) / 100;  // 0.73
var ultimaParcela  = parseFloat((cadaParcela + restoDivisao).toFixed(2)) // 0.77
// --------------------------------------------

Final result:

0.73 * 11 = 8,03 + 0,77 = 8,80

With this formula you must only ensure that it will be applied ((numeroDeParcelas - 1)*cadaParcela) + ultimaParcela and its sale will have the full value received, provided it is limited to only 2 decimal places. To increase the number of houses (as generally gas stations do) you must change the calculation of the rest of the division to 1000 instead of 100.

  • yes that way it works but if when I have a reference where the value of the sum of the plots is higher, when I use its calculation it does not arrive at the solution, var parcelas = 6, valor =10; var resto = (((valor) * 100) % parcelas) / 100; var cada = parseFloat((total / parcela).toFixed(2)); var ultima = parseFloat((cada + resto).toFixed(2)); //resultado ~> ((6 * cada) + ultima).toFixed(2) = 10.06

  • 1

    @Leandroluk edited the variable calculation cadaParcela to avoid this problem.

1

Convert decimal to int by multiplying by 100 before module operation and then dividing by 100 again.

  • I tried and even got to this solution before seeing that you posted but if I do this I value only the cents... I found another solution ja vlw

1

My solution was to do the treatment of each set of double characters, and after that adding them up. so:

var restoDivisao = parseInt(total % parcelas) + (((total * 100) % parcelas) / 100);

EDIT: The above solution is actually not right because when the portions are larger the values may end up not beating (thanks to the little problem that @anieroesclareceu), but I found that if I calculated the error margin previously I can treat the values and add/subtract that margin in a last installment, so my financial is not wrong, so:

var margem = ((((total / parcelas).toFixed(2)) * parcelas) - total).toFixed(2);

By doing this you arrive at the difference when the monetary value (of 2 houses) does not match the original value before the division and from that, just add the same (in module) in the last installment generated, so

var arrayParcelas = [];
for(var i = 0, i < parcelas; i++){
    arrayParcelas.push({'id':i, 'valor': total / parcelas});
};

arrayParcelas[arrayParcelas.length - 1].valor = 
    arrayParcelas[arrayParcelas.length - 1].valor - (margem);

Making this, regardless of the margin of difference is positive or negative, the last installment will have this hit.

  • 3

    You read my answer, right? That solves the problem there, the rest of your system will remain wrong. No decimal values are represented in binary form as JS does. That is, it is localized gambiarra that makes it look like it solves the real problem.

  • Yes, I read your reply and I fully agree with you but in the context that I am working on this solution was satisfactory. = D

  • 1

    So you don’t understand her, but I’m not the one who’s going to have losses :)

  • 1

    I arrived at the solution partner, that certainly does not give me loss! ;D

  • 2

    Okay, stubbornness causes more damage than ignorance.

0

I made a modification to apply the difference in the first installment.

function getValorParcelas(precoTotal,numeroParcelas){
    var valorParcela            = parseFloat(precoTotal/numeroParcelas).toFixed(2);
    var valorPrimeiraParcela    = parseFloat(precoTotal-(valorParcela*(numeroParcelas-1))).toFixed(2);

    return { 'valor_primeira_parcela': valorPrimeiraParcela , 'valor_parcela': valorParcela, 'numero_parcelas': numeroParcelas };
}
  • That doesn’t completely answer the question.

Browser other questions tagged

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