5:12 You should have used sting argument in constructor Decimal('1.1') instead of Decimal(1.1). You have lost all you precision when created float 1.1, because float 1.1 is not exactly equal to 1.1 And that is because floats are represented in binary values, but value 1.1 in binary system is periodic, so it cannot be written precisely. And that is the reason of getting not exact values at 5:46 when setting precision equal to 50.
Agreed. Integers is the way to go. I have done this way for years. And I hate it when I see implementations using floats for money. You'd think it wouldn't matter since the float value is extremely close to the proper value, but programmers get tripped up when they run into things like testing a + b == c fails unexpectedly (for example, when a = 1.1, b = 1.2 and c = 1.3.)
Storage of the values is almost always a concern. If you can’t store the format directly, that’s a big strike against the Decimal format. I worked with the Stripe API for two years. It integrated with other code that didn’t store it that way, but ultimately i was convinced that integers were the cleanest solution. Creating a class for representing currency as an int under the hood while providing display methods makes a ton of « cents » haha sorry. I often prefer not to make wrapper classes but in this case since storage and display are different it’s a good place for conversion methods that would probably otherwise go in a « util » class.
I was surprised to see a tail of garbage digits on Decimal value at 5:33. But this is because you initialise Decimal value with float literal 1.1, which is not equal to 1.1. You shall use string "1.1" instead.
Using integers as currency value makes sense in simple application. But in trade it is often required to use amounts smaller than 1 cent (i.e gas price with 1/9 cents in the tail). So, Decimals is the way to go.
Very useful Arjan! I do have those sorts of issues in my programs. Very nice to see that example with the money class, might also help with conversion between currencies!
Thank you for your impressive tutorial. I am learning Django and made some projects but I wanted to dig deeper and understand how the framework works internally. I checked the source code but it was intimidating for me. Would you mind making tutorials on building such a framework from scratch? including template engine, views, routing,db integrations, etc.
I think that representing money with integers is quite widely used. In my company we do that for other physical values like speed (25.10 km/h becomes 2510 [100km/h]) or anything which has a unit (Volts become mV, amps become mA etc...) One advice to avoid confusion for us programmers when dealing with such representations is to include the units in the names of the variables /function etc... For instance with a function "def calculate_average_current(currents_mA: list[int]) -> int", it's clear that the list of currents to give to the function should be in mA. I like this a lot but it breaks the PEP8 naming conventions, which is not great...
Hey Arjan, great video. Is it possible that you could make a video about the pint package for representing scientific units? It would be especially interesting to see yourvtake on typehinting and error handling in that case :)
Using ints for cash amounts is even easier in more recent Python versions, as underscore_separators can be added anywhere between digits of numeric literals (e.g. for thousands separators). So the cents can be distinguished visually in your source code from the Euros, by writing 100_00 instead of 10000. Hardcoding prices in source code is not a good idea however, but maybe it's useful for storing other fixed cash amounts (e.g. a threshold for money laundering checks?) and making test code more readable.
Two remarks: You partly test Python's velocity to call functions, thus a loop within the function would be better, next you should int() the Integer, round(,2) the float result and around() the numpy result. This changes the result quite significantly, larg int being the fastest, and numpy and VL floats somewhat five times slower...
Wooow🧠, Thanks a lot. This approach that is not language dependent🦾. You just cured my money handling headache in not just python but all programming languages.👏
Dude! This is crazy i wish i could share u our git repo, i watch all ur vids but havent seen this one! Decimal being slow and incorrect is halarious, we in our use case had to use fractional cents because of LLM cost per 1000 tokens was like 0.00025, we um settled on closest like 50 (rounded) was good enough, but i might make a PR to raise it to the token, just by going from 1e6, to 1e8 I guess it makes sense my code emulates urs as i watch a bit, still eerie tho
@arjan A couple of asks for recent python update... The Ruff linter is rising rapidly in popularity, as is the black formatter and poetry. All together this changes project startup significantly as there are configuration settings for Ruff and Black, to add to pyproject.toml. Then, docker seems to have issues with dealing with poetry configurations? Enjoying your work.
I believe that for the dunder methods for add and sub you should say if not isinstance: return NotImplemented if you want those to be more "standardized" but for this example yeah, not strictly required at all
How would you deal with problems such as interest and taxes? Store everything as ints, but when doing those calculations use the max precision floats, then store the final results as ints?
Looking at the comments it seems that one more, more advanced video on that topic would be useful. I would like to see more details on division of floating point numbers and also rounding.
Hi Bala, my channel focuses on more advanced and intermediate programming concepts. There are tons of other channels that focus on beginners. I don't think that fits with my audience.
A long time ago, someone imported this model of information to me: when, doing financial computation, always use a type that implements, the same rounding rules as your tax office uses. Cause if you disagree, you’re just asking for the world of hurt
I had similar code and found that multiplications and division were not accurate. Int(24.23 * 100) gave 2422 as a result. Solution was to use round before converting to int
Calculating interest is a pain. We would need more precision, at least 5 places after the decimal point, so instead of using a multiplier of 100 we would need to use a multiplier of 100000. Or you can just use floats and round to the place you need before comparisons.
Could you talk NumPy in the future... I've just met it for the first time and the documentation is like "hey where the hell is the actual documentation?"
Great video ! Did you heard about mojo from modular ? It's a new language with a syntax that is a superset of python. It's for AI computing but it's faster than python by default (and integrate other features like ownership, parallelism, hardware finetuning, etc.). It can be a future language that can replace python
5:12
You should have used sting argument in constructor Decimal('1.1') instead of Decimal(1.1).
You have lost all you precision when created float 1.1, because float 1.1 is not exactly equal to 1.1 And that is because floats are represented in binary values, but value 1.1 in binary system is periodic, so it cannot be written precisely.
And that is the reason of getting not exact values at 5:46 when setting precision equal to 50.
Thank you. I was wondering about that.
Good point - totally missed that!
Note, if you pass the values to Decimal as *string* instead of float, you won't get those garbage values at the end.
Agreed. Integers is the way to go. I have done this way for years. And I hate it when I see implementations using floats for money. You'd think it wouldn't matter since the float value is extremely close to the proper value, but programmers get tripped up when they run into things like testing a + b == c fails unexpectedly (for example, when a = 1.1, b = 1.2 and c = 1.3.)
"fails unexpectedly"
It's totally expected if you understand binary representation 😜
Arian, you can write large numbers such as 10000 as 10_000. Much easier to read
Storage of the values is almost always a concern. If you can’t store the format directly, that’s a big strike against the Decimal format.
I worked with the Stripe API for two years. It integrated with other code that didn’t store it that way, but ultimately i was convinced that integers were the cleanest solution.
Creating a class for representing currency as an int under the hood while providing display methods makes a ton of « cents » haha sorry. I often prefer not to make wrapper classes but in this case since storage and display are different it’s a good place for conversion methods that would probably otherwise go in a « util » class.
great dad joke
I was surprised to see a tail of garbage digits on Decimal value at 5:33. But this is because you initialise Decimal value with float literal 1.1, which is not equal to 1.1. You shall use string "1.1" instead.
Using integers as currency value makes sense in simple application. But in trade it is often required to use amounts smaller than 1 cent (i.e gas price with 1/9 cents in the tail). So, Decimals is the way to go.
Very useful Arjan! I do have those sorts of issues in my programs. Very nice to see that example with the money class, might also help with conversion between currencies!
that summation in the end is great! Keep doing that!
Thank you!
Arjan's two passions: Money and representing money adamantly on Python. That's a man who knows his life's priorities, damn.
hahahha! That's right!
Thank you for your impressive tutorial. I am learning Django and made some projects but I wanted to dig deeper and understand how the framework works internally.
I checked the source code but it was intimidating for me. Would you mind making tutorials on building such a framework from scratch? including template engine, views, routing,db integrations, etc.
I think that representing money with integers is quite widely used. In my company we do that for other physical values like speed (25.10 km/h becomes 2510 [100km/h]) or anything which has a unit (Volts become mV, amps become mA etc...)
One advice to avoid confusion for us programmers when dealing with such representations is to include the units in the names of the variables /function etc... For instance with a function "def calculate_average_current(currents_mA: list[int]) -> int", it's clear that the list of currents to give to the function should be in mA. I like this a lot but it breaks the PEP8 naming conventions, which is not great...
shouldn't that be 2510 m/h ?
Hey Arjan, great video.
Is it possible that you could make a video about the pint package for representing scientific units? It would be especially interesting to see yourvtake on typehinting and error handling in that case :)
Another great video thanks Arjan
You're very helpful, thank you very much.
Thank you!
Super helpful! Would've liked to've seen the timeit results of your custom class at the end though.
Using ints for cash amounts is even easier in more recent Python versions, as underscore_separators can be added anywhere between digits of numeric literals (e.g. for thousands separators). So the cents can be distinguished visually in your source code from the Euros, by writing 100_00 instead of 10000. Hardcoding prices in source code is not a good idea however, but maybe it's useful for storing other fixed cash amounts (e.g. a threshold for money laundering checks?) and making test code more readable.
I think this approach makes more sense.. I do a lot of things with money in pyspark applications I will try this method and add more comments..
Two remarks: You partly test Python's velocity to call functions, thus a loop within the function would be better, next you should int() the Integer, round(,2) the float result and around() the numpy result. This changes the result quite significantly, larg int being the fastest, and numpy and VL floats somewhat five times slower...
Wooow🧠, Thanks a lot. This approach that is not language dependent🦾. You just cured my money handling headache in not just python but all programming languages.👏
You are very welcome!
Dude! This is crazy i wish i could share u our git repo, i watch all ur vids but havent seen this one!
Decimal being slow and incorrect is halarious, we in our use case had to use fractional cents because of LLM cost per 1000 tokens was like 0.00025, we um settled on closest like 50 (rounded) was good enough, but i might make a PR to raise it to the token, just by going from 1e6, to 1e8
I guess it makes sense my code emulates urs as i watch a bit, still eerie tho
Amazing
@arjan A couple of asks for recent python update... The Ruff linter is rising rapidly in popularity, as is the black formatter and poetry. All together this changes project startup significantly as there are configuration settings for Ruff and Black, to add to pyproject.toml. Then, docker seems to have issues with dealing with poetry configurations? Enjoying your work.
Good suggestions, Evan, am already looking into these things!
Another great video, loving all the python information you give out for free. Could you do a video talking a little about pyspark? =)
as always, very good video
Thank you!
I believe that for the dunder methods for add and sub you should say if not isinstance: return NotImplemented if you want those to be more "standardized" but for this example yeah, not strictly required at all
How would you deal with problems such as interest and taxes? Store everything as ints, but when doing those calculations use the max precision floats, then store the final results as ints?
Looking at the comments it seems that one more, more advanced video on that topic would be useful. I would like to see more details on division of floating point numbers and also rounding.
Awesome video, thanks
Glad you enjoyed it!
"All progress takes place outside the comfort zone." -Michael John Bobak
What about space complexity with using a Money class.
Arjan, please please put together a Python course from Basic to Advanced. I am sure there will be a lot of people who would be interested
Hi Bala, my channel focuses on more advanced and intermediate programming concepts. There are tons of other channels that focus on beginners. I don't think that fits with my audience.
A long time ago, someone imported this model of information to me: when, doing financial computation, always use a type that implements, the same rounding rules as your tax office uses.
Cause if you disagree, you’re just asking for the world of hurt
Aha, no longer Arjan the Blue, but Arjan the Grey and Arjan the Black!
I had similar code and found that multiplications and division were not accurate. Int(24.23 * 100) gave 2422 as a result. Solution was to use round before converting to int
is 0 the same as 00 in python?
How do you deal with tax calculations? Add 19% tax to 1,71€ is 171*119/100=203,49. Round, truncate?
Typically it will be specified by the relevant law/regulations. Devs don’t get a choice here.
Remember BCD (Binary Coded Decimal)? Those were the days. ;)
Calculating interest is a pain. We would need more precision, at least 5 places after the decimal point, so instead of using a multiplier of 100 we would need to use a multiplier of 100000. Or you can just use floats and round to the place you need before comparisons.
I just use the decimal type in Django 😜
I prefer Integer + Money class, this is the actual domain modeling approach.
Could you talk NumPy in the future... I've just met it for the first time and the documentation is like "hey where the hell is the actual documentation?"
This is how Richard Pryor stole all the half pennies in Superman 3.
Great video ! Did you heard about mojo from modular ? It's a new language with a syntax that is a superset of python. It's for AI computing but it's faster than python by default (and integrate other features like ownership, parallelism, hardware finetuning, etc.). It can be a future language that can replace python
Arjan, your videos are great but also terrible. I used to be happy, but then I watched your channel, and now I hate everyone's code. :)
Hahaha! Thank you!
No person on Earth should be allowed to own more money than the amount representable by a 64 bit number.