Calculate the rest of a division of decimal numbers in JavaScript

3

I'm working with values ( decimal(18,2) ) of a sale where the sum of the price of the products should turn into a certain number of parcels. So that I can divide the total of the products exactly for the parcels, I also have to calculate the remainder of the division and then apply this leftover to a single final parcel. For this 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 for the rest in the split, it is returning 0.30 but if I split 30.3 / 3 the result is 10.1 .

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

Testing, in a way I was able to do this:

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

This works, but I can not really accept that there is no "cleaner" method. Is there a more visually correct way to get to the rest of this division?

    
asked by anonymous 08.01.2016 / 14:08

6 answers

1

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 data:

var parcelaObj = getValorParcelas(100,3);
alert(parcelaObj.valor_primeira_parcela);
alert(parcelaObj.valor_parcela);
alert(parcelaObj.numero_parcelas);
    
10.11.2017 / 04:31
7

You've just found out what every programmer should know before making a code that deals with money. "Decimal" values are actually binary and are not exact. For most things that need non-integer values this inaccuracy does not really matter, with money and some other types of data matters. JavaScript by default works with the binary value.

Maybe you have no error in the algorithm but I will not say the operation is right because I did not see all the code that distributes the plots . Unless that's all. So I do not understand this as calculation of plots and there is a very wrong logic.

You can do whatever you want with binary data that will not solve. Probably your entire system has these problems and possibly causing financial errors. And no use resolving this calculation alone. It will settle at this point and continue to cause problem in the rest of the system. Never believe in solutions that "work", follow right solutions. What works may fail because it was not right. Always learn to the right.

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

The solution is to change the way you store and manipulate all of 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 I already replied to: How to represent money in JavaScript? . There is the solution using only integers or libraries that somewhat abstracts its use, but does not solve all situations.

    
08.01.2016 / 14:17
3

I read all the answers and I did not see any of them laying out the root of your problem. Your problem is actually not in JavaScript (or programming), but rather in the mathematical logic behind the Módulo operation.

To better understand, let's go to the most basic partition model possible:

Please note that the first operation is 30 ÷ 30 , with 1 result and 3 remainder. When you get the rest 0.3 , it's the 0.3 you're getting. The answer is not 0.1 , as 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 picking up the decimal values up to 2nd precision and making them whole (as the 2nd series division suggests you do) and then calculating the rest.

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

See that 14,28 * 7 = 99,96 , with the remaining 0.04 being the remaining R $ 100.00

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

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

See that 0,73 * 12 = 8,76 is 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 should only ensure that ((numeroDeParcelas - 1)*cadaParcela) + ultimaParcela will be applied and your sale will have the full amount received, as long as it is limited to only 2 decimal places. To increase the number of houses (as usually petrol stations do) you should change the calculation from the rest of the division to 1000 instead of 100.

    
08.01.2016 / 17:34
1

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

    
08.01.2016 / 14:15
1

My solution was to treat each double character set, and then add them together. like this:

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

EDIT: The above solution is not really true because when the plots are larger the values may end up not hitting ( thanks to the problem that @bigown clarified), but I found that if I calculate the margin of error earlier I I can handle the values and add / subtract that margin in a last installment, so my financial does not go wrong, like this:

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

When you do this you get to the difference when the monetary value (of 2 houses) does not match the original value before dividing and from there, just add the same (in module) in the last generated part, 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);

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

    
08.01.2016 / 14:28
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 };
}
    
05.04.2018 / 21:42