Problem with currency percentage and rounding

Asked

Viewed 884 times

4

How to maintain monetary values with only 2 decimal places when performing percentage operations? Example:

  • Total value: R$ 377,17
  • Percentage divisions: 33%, 33% and 34%

Problem: the sum of the value of the plots is not equal to the total value.

Code:

public static decimal Calcular(decimal valor, int percentual)
{
    var fator = percentual / 100M;
    return decimal.Round(valor * fator, 2);
}

Testing:

[Theory]
[InlineData(new[] { 39, 60, 1 }, 0.12)]
[InlineData(new[] { 33, 33, 34 }, 377.17)]
public void DeveCalcularPercentualComSucesso(int[] percentuais, decimal valorTotal)
{
    var resultados = new decimal[3];

    for (int i = 0; i < percentuais.Length; i++)
        resultados[i] = Percentual.Calcular(valorTotal, percentuais[i]);

    var soma = resultados.Sum();

    soma.Should().Be(valorTotal);
}
Expected soma to be 377.17M, but found 377.18M.
  • What is your problem? it is returning integer values ?

2 answers

4


The calculation is being done conceptually wrong. It’s not just a rounding problem. Although rare, only not rounding can give difference the same way because at a point there at the end of the precision can give a minimum difference in some cases (it will not happen when percentages are integer).

In fact I think this method is still conceptually wrong because it meets a conceptually wrong definition. It is wrong and may even be illegal in specific cases to parcel a value this way with arbitrary percentages (in some cases it may). In general the most correct is to say the number of plots and the criterion of what to do with the difference that in many cases will occur. This difference is usually 1 or a few cents.

The criterion can be put in the first, the last, if it is more than a penny put in the first as distributed as possible, or in the last, and can even detail what to do when there is difference even in the distribution, although rare can even say specific positions, in some cases has legislation regulating it. In some cases one accepts difference for more or less by own legislation or convention, but in this particular case it is not what one wants. So this method is just a basis of how to solve, note that it is fixed that the difference is in the last.

The most correct would be to validate if the sum of the percentages is 100, but I preferred to let the programmer do it right since you usually do unit tests. One of the advantages of this type of test is that if it is done right avoids cost of performance to validate the argument. Tests like this have disadvantages also that nobody talks, but this is another subject.

Companies and programmers in general do not understand how the routines of their operation should work and are always at risk of government fines, contracts or having other problems.

using static System.Console;
using System.Collections.Generic;

public class Program {
    public static void Main() {
        foreach (var item in CalculaPercentuais(377.17M, new decimal[] { 33.0M, 33.0M, 34.0M })) WriteLine(item);
    }
    public static IEnumerable<decimal> CalculaPercentuais(decimal valor, IEnumerable<decimal> percentuais) {
        var valores = new List<decimal>();
        foreach (var percentual in percentuais) valores.Add(decimal.Round(valor / 100.0M * percentual, 2));
        var soma = 0.0M;
        for (var i = 0; i < valores.Count - 1; i++) soma += valores[i];
        valores[valores.Count - 1] = valor - soma;
        return valores;
    }
}

Behold working in the ideone. And in the .NET Fiddle. Also put on the Github for future reference.

  • Man, thank you so much for the explanation. I’m just trying to avoid distortions in the calculations. Including the difference in one of the elements has been the best output for now.

  • Just pay attention to the entire text because I don’t even know if this shape is correct. Now you can vote for everything on the entire site.

1

The problem is rounding. You’re calculating the percentages and rounding to 2 decimal places and then adding up the rounding and this causes the difference.

For the sum of the plots to be exactly equal to the initial value, you should not round the value, this rounding, if necessary, should be done only at the end.

Below is an example of 377.17:

377,17 * 33% = 124,4661 - Rounding with decimal.Round is 124.47

377,17 * 34% = 128,2378 - Rounding with decimal.Round stands 128.24

124,4661 + 124,4661 + 128,2378 = 377,17

124,47 + 124,47 + 128,24 = 377,18

If you want to show the rounded value to the user, you can display the rounded value but should keep this value without being rounded so that at the end the sum is exact.

Browser other questions tagged

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