Problem with pennies when using the Biginteger type

Asked

Viewed 1,015 times

7

I’m having a problem adding up pennies. Informo 1200, returns 1.200,00 instead of 12,00.

Code:

formatar_data(1200);

private int formatar_data(int inteiro){     
    BigInteger  valor = new BigInteger(String.valueOf(inteiro));  
    NumberFormat nf = NumberFormat.getCurrencyInstance(new Locale( "pt", "BR" )); 
    String formatado = nf.format (valor);
    System.out.println(formatado);
    return inteiro;
}

4 answers

12


Use double or float To represent money is to ask for trouble and cause harm. These are types with the binary fractional part and cannot represent all possible values.

These types are great where accuracy is not required. What is not the case with money.

One of the most common techniques for representing monetary values is to use integers. That is, each unit is equivalent to a penny. When it needs the fractional part, it is divided by the result of 10 high to the number of decimal places it needs.

It is common for languages to offer their own type for this by correctly treating decimals as decimals. In Java this type is BigDecimal (unfortunately there is no shorter type, that spent "a lot" memory).

Another possibility is the use of Joda Money. It can be overkill for many cases. A variation of it seems to be being considered for inclusion in standard Java.

That has already been answered in that reply and advise to read and follow the links for more details.

I would rethink all implementation and decide now with more subsidies how you want to work with monetary values. Although the integers are interesting, not everyone wants to deal with the details of it and prefers a guy who does it for the programmer.

If you continue in one piece, you’ll need to change everything you have to deal with the pennies. Also understand that number is different from textual representation of number. With numbers you do accounts, but when it presents numbers in the background is dealing with texts, porting formatted data are always texts:

private String formatar_data(int inteiro, int casas) { //mudei o tipo do retorno
    NumberFormat nf = NumberFormat.getCurrencyInstance(new Locale( "pt", "BR" )); 
    String formatado = nf.format(inteiro / (Math.pow(10, casas)));
    System.out.println(formatado); //imagino que isto é só para testar
    return formatado; //eu eliminaria esta variável
}

I put in the Github for future reference.

As some people deny the use of integers go some links:

  • Martin Flower (Other)

  • Wikipedia

  • Java tutorial

  • There are books I can’t get for a link here. I quote the book Effective Java:

    The right way to Solve this problem is to use Bigdecimal, int or long for monetary calculations.

Several languages do not have a type that manipulates money abstractly and the use of the integer is the only option. This has been used for years without problems in software running for decades.

The one from BigDecimal is just an abstraction for the use of a whole and this gives some ease. It gives some facilities but takes away some flexibility and power. With abstraction or no calculation policies, rounding, scaling and other transformations should be used at the discretion of the programmer. In certain circumstances, a policy can be abstracted into some specific type, but not a general type like the BigDecimal.

A detail that few people know can even use float or double to represent money and the like. Since you ensure that you will only use integers, after all the integers can be accurately represented in these types. Of course the programmer would have to guarantee this, so it makes little sense to do this, but it is of curiosity, not recommendation, on the contrary.

  • 1

    Living and learning, for me it was absurd to treat currency as a whole. Now everything makes more sense.

  • 1

    It’s absurd to treat currency for real as a whole, see my answer. To keep, present or preserve finite precision numbers floats and doubless are not appropriate, but this does not imply that the best answer is whole. It has memory and database types specific to finite precision numbers. They must be used.

  • 6

    @Andrélfsbacci anguns of the most knowledgeable on the subject disagree with you. Anyway I didn’t say what’s best, I said it’s a better situation than float. But I am far from saying that this is absurd. That would be absurd. Several softwares are made like this by very competitive people for a long time. The fact that you do not like the solution does not mean that it is not effective. You can choose whatever you want for yourself but don’t impose your taste on everyone.

7

To continue using the same type, you can multiply all values by 100. Since pennies don’t vary in the number of decimal places, it’s easy to keep all your data consistent and by using a whole type, you get better accuracy on all accounts.

When displaying to the user, you will need to divide by 100 all values, of course.

  • 2

    +1. And above all, it gains processing performance and storage efficiency. To be easy to manage, when it comes to the database, we use here the nomenclature value100, discount100, quantity1000 in the fields, so it is clear the division factor.

6

Problem response

From what I understand, you want to format an "integer that internally has two decimals" as a monetary value. You handled money, the answer necessarily goes through Bigdecimal.

private static String formataPtBr(int inteiro)
{
    BigDecimal hundred = BigDecimal.TEN.pow(2);
    BigDecimal value = BigDecimal.valueOf(inteiro).divide(hundred);
    NumberFormat nf = NumberFormat.getCurrencyInstance(new Locale("pt",
            "BR"));
    String formatado = nf.format(value);
    // System.out.println(formatado);
    return formatado;
}

Six things:

  1. You can do it in other ways, with int, string? Yeah, but then it opens up unexpected rounding and trucking problems. It will work in 99.99999% of cases, but with 99.99999% certainty it will cost you whole nights badly slept, hunting these heisenbugs. None of the solutions presented mention fpstrict, so there is zero guarantee that the result you see on your machine is what will be calculated on another.

  2. Name: format_data? Best something much more specific, camelCase: formataBrl(), formataPtBr() or something like that. It matches more with the content of the function.

  3. Using a primitive type for something very specific and different from the original is a code Smell. Maybe I’ll find a Overkill create a specific type for number, but if you are making a large and serious system, this is a decision that you going repent.

  4. Related to the previous, you are doing x = formata_data(valor) instead of x = valor.formataPtBr(2). Another sign that things are poorly structured.

  5. Having a specific type will force you to use the always correct type and that’s good. Will avoid to pass the whole unintentionally in a place that accepts whole, at the time of debugging you will always see the correct formatted result, without mental juggling.

  6. If you’re worried about performance, create singletons of hundred and nf. Where to put? All the more reason to create a specific class for this kind of money which is an "integer with two decimal places".

Bigdecimal is ready, is standard, well tested and covers all the above points.

Whole for coins virtual

There is a situation in which the use of integer for coins is acceptable. The case of virtual coins, fake. I’m talking here about scores, digital games money, stuff like that. In this kind of context, where there’s a lot of non-decimal fractionation this could be interesting.

For example, in a virtual world where the smallest denomination is copper, which is equivalent to 100 silvers, which in turn is equivalent to 100 golds, it is interesting to keep the money balance in the smallest denomination (copper), as this facilitates all accounts. Only at the time of showing that, then yes, it makes itself the separation of the factors in a way "for humans", leaving the whole shape for the machine to deal.

Out of that context is a bad idea.

Decimal for coins real

'Cause the whole thing is a good idea for pretend coins, and a bad idea for real coins? In a nutshell: division, multiplication, exponentiation, square root, rounding.

Fake coins usually only operate sums, subtractions, multiplications. Real coins are quickly wrapped in operations financial, involving higher mathematics, which demand decimal extras, partial to function properly. This is impossible in a "no scale" type, as is the case of using fixed integer with two decimal places.

And these are the most "simple" operations. And if you’re having trouble putting decimals in the representation, it doesn’t look like you’re going to even hit those other algorithms. Performance, then, forget it.

Catch something simple, round up. It has five hundred types of rounding, all different from each other, and which exist because they are necessary in certain types of formula. An integer type with two decimals nor has to round up in addition to the two decimal places, from which it is deduced that such a type will not even function in these formulas.


So, seriously. If you’re dealing with real money, use Bigdecimal.

If you don’t want to use Bigdecimal, at least create a "Mymoney" type, to differentiate this case from others, and hang appropriate methods on it, instead of loose functions in the code.

  • 4

    Although your answer is correct it makes me want to negatively say that the solution is necessarily to use BigDecimal. This is not true. This is just one of the solutions. The fact that you limit yourself to solutions does not mean that this is correct. You can make whatever choice you want, but don’t impose your solution on other people when there are alternatives. In fact the whole does not have the problems you are talking about. Or even have, the same problems of BigDecimal.If you do not know how to handle the scale correctly, you will have problems with both.There is no complete answer here

4

BigInteger and Intenger (int) are types of data used to work with integer. The guys Double and Float are most recommended to work with decimal numbers.

However, if you are working with coins and need to use integers because of the accuracy, you must exchange the return of your method formatar_data of int for String. It would look something like:

private String formatar_data(int inteiro){
    DecimalFormat df = new DecimalFormat("#.00"); // Formato com 2 casas decimais
    return df.format(inteiro/100.0);
}
  • 1

    Hear that to Represent a currency it is better to use integers. Another the database is returning all integer.

  • 1

    To represent a coin is it better to use integers? Where did you "hear" say that?

  • 1

    It is impossible to format an integer with decimal places, either you use the correct data type or you will have to work with strings in the middle of the process.

  • 1

    @Hy-Razil See how you’d be returning String.

  • 2

    About where you "heard". In the book Martin Fowler in his book Patterns of Enterprise Application Architecture and Bloch also advocates the use of integers in the book Effective Java.

  • 1

    @Hy-Razil I understood what you meant. Note that I edited my answer, it should suit you. You can’t "format" a variable int with decimal places, it will be necessary to use String.

  • 1

    @jbueno ok, thank you.

Show 2 more comments

Browser other questions tagged

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