4

I have a list and it contains a certain number '5.74536541' in it which I convert to a float.

I am printing it out in Python 3 using ("%0.2f" % (variable)) but it always prints out 5.75 instead of 5.74.

I know you're thinking who cares, but it is for a currency converter program and I don't want the currencies to round up/down but to be exact.

How can I keep it from rounding but also keep the 2 decimal places?

Goose
  • 2,130
  • 10
  • 32
  • 43
  • 1
    http://stackoverflow.com/questions/783897/truncating-floats-in-python – cnicutar Dec 04 '12 at 03:42
  • If you are certain that the string is a number, then you can just process the text directly. – nhahtdh Dec 04 '12 at 03:42
  • Your question doesn't make sense, 5.75 is closer to the number than 5.74. Keep the numbers stored as floats, and let them print with rounding - what's the problem? – wim Dec 04 '12 at 03:44
  • 1
    @wim: Didn't he mention the reason in the question? `I know you're thinking who cares, but it is for a currency converter program and I don't want the currencies to round up/down but to be exact.` – nhahtdh Dec 04 '12 at 03:45
  • @wim When I use Google's currency converter it converts to 5.74 not 5.75, I am using that to be exact. – Goose Dec 04 '12 at 03:45
  • 2
    That is not 'exact' though. The proper way to do it is have all the maths in full precision (possibly even using decimal module, for financial stuff) and only round for printing purposes. – wim Dec 04 '12 at 04:02
  • I don't think "exact" means what you think it means – John La Rooy Dec 04 '12 at 06:03

5 Answers5

9

You shouldn't use floating point numbers for currency, due to rounding errors like you mentioned.

Your best bet is to use a fixed-precision decimal where you also have full control over how rounding and truncation works. From the docs:

>>> from decimal import *
>>> getcontext()
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999,
    capitals=1, flags=[], traps=[Overflow, DivisionByZero,
    InvalidOperation])

>>> getcontext().prec = 6
>>> Decimal('3.0')
Decimal('3.0')
>>> Decimal('3.1415926535')
Decimal('3.1415926535')
>>> Decimal('3.1415926535') + Decimal('2.7182818285')
Decimal('5.85987')
>>> getcontext().rounding = ROUND_UP
>>> Decimal('3.1415926535') + Decimal('2.7182818285')
Decimal('5.85988')

You should represent all currency-based values internally as Decimals with a high precision (the standard level of precision should be fine in your case - just leave the prec alone!). If you want to print a nicely formatted dollars and cents value to the user, using the locale module is a straightforward way to do this.

Be careful when printing as you will have to quantize the Decimal down to the correct number of places for display or the rounding will not be based on your Decimal context! You should only perform the quantize step for final display or for a single, final value - all intermediate steps should use high-precision Decimals to make any operations as accurate as possible.

>>> from decimal import *
>>> import locale
>>> locale.setlocale(locale.LC_ALL, '')
'en_AU.UTF-8'
>>> getcontext().rounding = ROUND_DOWN
>>> TWOPLACES = Decimal(10) ** -2
>>> var = Decimal('5.74536541')
Decimal('5.74536541')
>>> var.quantize(TWOPLACES)
Decimal('5.74')
>>> locale.currency(var.quantize(TWOPLACES))
'$5.74'
John Lyon
  • 11,180
  • 4
  • 36
  • 44
  • I'm doing getcontext().prec = 2, then var = Decimal('5.74536541') but it just prints out 5.74536541? – Goose Dec 04 '12 at 03:54
  • 1
    When you create a new `Decimal` object, it is created with the precision required to represent that object (see in the example above where Pi is input with more than 6 significant figures after setting the context). Doing an operation on that decimal should return a new decimal object with the context's precision (e.g. `var - 0`). Are you sure you want such low precision? I think you might want to keep precision as high as required and `ROUND_DOWN` for final 2-digit display? Also, prec = 2 should yield `5.7` for your example. – John Lyon Dec 04 '12 at 03:57
  • That's odd, the docs say >>> getcontext().prec = 6 >>> Decimal(1) / Decimal(7) returns >>> Decimal('0.142857'), but I tried that and it isnt working for me. Does prec = 2 mean that it keeps 2 decimal places or am I thinking of something else? And unfortunately yes I need it to 2 decimal places, because $5.74 exists but $5.745 is not a real currency value. – Goose Dec 04 '12 at 04:01
  • The significance of a new Decimal is determined solely by the number of digits input. Context precision and rounding only come into play during arithmetic operations. Note that precision controls significant figures, not necessarily decimal places. Try with some bigger numbers to test that out! – John Lyon Dec 04 '12 at 04:05
  • To summarise, you should do all of your calculations with `Decimal` objects and with a high level of precision. For printing dollars and cents values, consider using `locale`. I'll update my answer with an example of this. – John Lyon Dec 04 '12 at 04:10
  • Thanks, I somehow got it to work by just using the Decimal mod. – Goose Dec 04 '12 at 06:28
  • It is a little bit complicated, but then, you want to have calculations that are accurate, round down/truncate your result in a specific way, and print the output. When dealing with currency, it's always important that accuracy comes first. What if you rounded incorrectly for thousands of transactions? [Salami Slicing](http://en.wikipedia.org/wiki/Salami_slicing), ala Office Space, or Superman III. – John Lyon Dec 04 '12 at 06:29
4

If you're dealing with currency and accuracy matters, don't use float, use decimal.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
1

Floating point values are known as "useful approximations". Whatever you do to a floating point number—round it, truncate it, whatever—if the result is a floating point value, you don't get to decide how many digits to the right of the decimal point it has.

Never use floating point values for currency. See pydoc decimal, for example. Python's decimal module supports decimal fixed point and decimal floating point arithmetic.

Python docs warn about rounding floats.

Note The behavior of round() for floats can be surprising: for example, round(2.675, 2) gives 2.67 instead of the expected 2.68. This is not a bug: it’s a result of the fact that most decimal fractions can’t be represented exactly as a float.

If you're not careful, you'll be misled by the value that appears at the interpreter prompt.

Python only prints a decimal approximation to the true decimal value of the binary approximation stored by the machine.

And

It’s important to realize that this is, in a real sense, an illusion: the value in the machine is not exactly 1/10, you’re simply rounding the display of the true machine value. This fact becomes apparent as soon as you try to do arithmetic with these values

Mike Sherrill 'Cat Recall'
  • 91,602
  • 17
  • 122
  • 185
1

Take away the number mod 0.01

i.e.

rounded = number - (number % 0.01)

then print it the same as before.

This said, rounding down is not more accurate. Are you trying the old steal money from a bank by exploiting rounding errors scheme?

Lucas
  • 1,869
  • 4
  • 20
  • 36
  • `This said, rounding down is not more accurate.` Bank will not give you more when you convert currency, though. It's a legitimate reason to truncate. – nhahtdh Dec 04 '12 at 03:57
  • It will take `0.01` even from `$2,000.00` which is wrong. – Kenly May 19 '21 at 11:05
-2

If the number is a string then truncate the string to only 2 characters after the decimal and then convert it to a float. Otherwise multiply it with 10^n where n is the number of digits after the decimal and then divide your float by 10^n.

Kartik
  • 9,463
  • 9
  • 48
  • 52