suggestion 1
a good practice in such cases, if the accuracy is not too critical is to let Python store the value of the accounts as they are made, without worrying about that value - and, just at the time of present value to users you round with the number of desired decimal places.
In doing so, if you have several operations to perform with some numbers, Python stores all intermediate results in the maximum accuracy allowed by 64-bit floating point numbers. This causes errors that could be entered in your accounts by a rounding before the time are cancelled at the end.
For the representation, what you have to keep in mind is the following: the number is in a Python variable, and at some point your program will generate the output for the end user. It doesn’t matter if this output is in an HTML snippet, if it is a web application, it is shown in a print on the terminal, it is shown in a widget on the screen, it is exported in a CSV-type file, or it is saved in a database: it is only then that the smallest accuracy matters. And in all of the above cases, and a few more, the number is serialized as a string of decimal digits. (The only exception is the database, if it is only consumed by its application proria back, can leave the number without rounding in the database as well).
Then, you can use the string formatting syntax, with the new Python 3.6 f-strings, to specify the exact number of decimal places you want, and the language rounds to you - including taking care of cases of broken numbers because they cannot be represented correctly on the binary basis - which is the native format of floating point numbers.
For example, in your case, with
preco_por_literro = 5.83
km_wheelsets = 22
litros_supplied = 1
preco_por_km = preco_por_literro * litros_abastecidos / km_rodados
If you print "preco_por_km" with print(preco_por_km)
, arrives in the 0.265
-
but if you use string formatting, you can do:
print(f"Preço por km: R${preco_por_km:.02f}")
The "f-strings" { }
in a string, and, if after the expression, there is the delimiter :
, apply on the result to formatting mini-language. In case we have :02f
, meaning "a floating point number with two boxes after the decimal point, filled with 0
if the number is round".
suggestion 2
Another good practice - especially when dealing with financial values, is, instead of using native floating point numbers, which has limitations to represent some decimal numbers, and other rounding restrictions, to work around with decimal numbers. In Python, these are another numeric type - the class decimal.Decimal
- in that case, after each number entry, and after each operation, the result is rounded to the desired number of houses - and the print will only have the desired number of houses (where the final presentation may be adjusted in the same way as the previous one, for example to include the "00" of the cents after the decimal point if the number is round).
There are two cool tips there: the first is that the standard precision of the decimals is large, so it is important to change the context to only have two decimal points. The second is that nobody wants to keep typing decimal.Decimal(1)
for each time you put a number in the program, then you can use the syntax from decimal import Decimal as D
to bear the name D
representing the class decimal.Decimal
from decimal import Decimal as D, getcontext, setcontext
# As próximas 3 linhas limitam o contexto dos números a duas casas decimais
ctx = getcontext()
ctx.prec = 2
setcontext(ctx)
preco_por_litro = D('5.83')
km_rodados = D(22)
litros_abastecidos = D(1)
preco_por_km = preco_por_litro * litros_abastecidos / km_rodados
print(preco_por_km)
And this will print 0.26
, only with two houses - only .026
instead of the 0.27
- on account of what I wrote above, by changing the context, the intermediate result of each operation is rounded to only two decimal places - then the ". 005" of the ". 265" gets lost in the path (even if you adjust the rounding extrateness of the decimal context - which is also configurable - it will only take effect if the internal representation of the numbers is greater than two decimal places), and you use the format of strings at the time of printing, as in the previous case.
extra information
Python has 3 rounding functions for floating point numbers - math.floor
, math.ceil
, that require the import of the module math
,and the function round
which is built-in (builtin). This usage, in addition to only rounding a number to the nearest integer, as happens in most other languages, allows a second parameter, optional, indicating the number of decimal places desired. You can use this round by passing "2" in the second parameter when printing your accounts' result, no string formatting, and so on - but you have to keep in mind that if you try to limit the number to two decimal places in your accounts' intermediate results, the code will be subject to the limitations that are part of how floating point numbers work (this link is the same as the second one above) - including, depending on the number, and the form of printing, it can "expand" again to more than two decimal places, even with the use of the "round". Example of use: print(round(preco_por_km), 2)
. The functions math.ceil
and math.floor
will always round to whole numbers.
The numbers of the type decimal.Decimal
, even if used internally with greater precision, has the method quantize
which permits rounding by passing a decimal number as a parameter 1.00
(to two decimal places). Ex.: print(preco_por_km.quantize(D('1.00'))
- if the context has more than two boxes will print 0.27
. (remember, with the Decimal
you can work with unlimited internal precision - just that operations with more than a few tens of thousands of digits will start to get really slow)
Use the round() function, search for it in the documentation.
– Cadu