-
Notifications
You must be signed in to change notification settings - Fork 45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Doing money calculation and perform rounding at the end #27
Comments
Is there a reason for not keeping the exact value in Money, and using that in all math operator overloads, and then only round when converting to another type (Amount-getter, ToString etc)? |
Rounding money isn't just a matter of presentation and doing this in a view or when converting to a different type. Whether money value is rounded is important domain logic, and is Money is based on a real agreed system and can't represent values out of its range. For Euro's to lowest positive value is a cent, €0.01, and you can't split this. That is what this type does for you, it protects you from passing unrounded money, which isn't valid. Your domain model should always return or store rounded values for Money. An invoice is a good example where using the unrounded value underneath for the calculation, but presenting it rounded, would get you in trouble (you are missing a cent).
Compare this with for example the Int type. When I do the following calculation So rounding should be the default, but of course there are exceptions where you want to do calculations and only do the rounding in the end or in certain in-between-steps. You are basically want to convert Money to a decimal type, do calculations and convert it back to Money again. The solution should be explicit that you want to work with unrounded values. |
Thank you for the explanation. |
I am considering using NodaMoney instead of developing my own money library; however, I see it's lacking in features in terms of manipulating money - i.e. working with unrounded amounts - although the foundation of what's already build seems solid. I suggest introducing a new type - let's say UnroundedMoney - to make the domain richer but concise. You can have functionality such as: |
Hi. I'm not against such a type, but I don't know if this is the way to go. Do you have clear use cases how and where unrounded money would be used? The problem is I think that everyone has different policies, to come up with a generic solution. |
Two common cases I'm familiar with are accrual calculations and distributions. Accrual calculation in particular is a type of calculation that often needs to be persisted as well as used outside its immediate scope of where it was originally calculated. It's not essential to have a type for this but there is some value in having more precise function signatures and data types. Who knows, it might save someone from silly bugs like forgetting to include a money variable in their multi-step calculation or including money twice in multiplication. |
Accrual calculations and distributions is not a clear enough use case. It think each company has it's own rules about how to do this. What is the rule you need and how do you apply it in a scenario? Post-rounding remainders? Could we solve this by adding a new extension method like so: |
This issue makes this library kinda useless it's like doing Also, I'm not sure if the default rounding For example, @remyvd have you come up with any idea yet? |
I've spent copious number of hours explaining to clients why two seemingly identical numbers on the screen result in an out of balance error message, and why things are 1 cent off. Had we enforced this constraint of respecting the domain's invariant (e.g. users, the tax office, the accountants, don't report on fractional cents), I will have a few years longer to live. I wouldn't use the Money type wherever I see Decimal - that would miss the intent of this library. I would take time to identify where the data needs to interface with the Accounting world at large and use it. In a version of an accounting domain, extending on the invoice line items example above, each line item wouldn't need Money to represent line total. The invoice total, definitely. Perhaps subtotals of types of line items calls for it too. Because they interface with general ledger via journal entries, and the recipient of the invoice. Both accountants and the recipient would be interested in the totals having the legal tender precision, and currency. As for the breakdown, use whatever precision desired, as long as it adds to the total - they don't interface with the accounting world. I like it the way it is, non-opinionated. |
It's not uncommon for prices to be defined in fractions of cents. I think it would be great for this library to support that, and make rounding amounts opt-in. For my use-case I need greater precision on the line items, and also be able to serialise with great precision. So having this library take care of calculations and returning a rounded value wouldn't work for me. I would simply want to opt-out of rounding. Take for example an Azure VM (B1S), which is currently priced at €0.0034/hour for my region. I wouldn't be able to use this library to perform any calculations based on that rate. Another example would be cash payments in the Netherlands: amounts are rounded to the nearest €0.05. When you have to pay €9.99 with a €10 note, you get no change. However I wouldn't want to use a software library that only allows me to use multiples of €0.05; I'd still need the €0.01 granularity on the line items. |
I understand what you're saying @Bouke, but I think we're talking about different things here. If you've only used 1 hour of that VM, Azure will not make you pay 0.0034 euro when they invoice you. They'll either not charge you anything, or charge you 0.01 euro. When it comes to doing calculations on what 18 hours of use would cost me, I certainly would NOT be using this library - all it's extra features over and above what Decimal offers are useless in this calculation. But when it comes to working out how much I've left to pay an invoice in EUR from Azure using USD, I would think so. |
For multiplying it does
@Bouke I think the difference lies where the money type is being used for: (Total) Amount or (Unit) Price. A Price is money/unit, which if multiplied by the unit result into money (=(Total) Amount). So: (Total) Amounts should I think always be rounded to prevent errors, but I understand that sometimes (Unit) Prices needs more precision. Maybe adding a type The example you give is a special type of price, an hourrate: money/hour or money/TimeSpan. This could be Do you see other places, besides price, where you need more precision? |
For my use-case I simply need the tuple of amount+currency, any business rules regarding the calculations involving this tuple will be up to my domain code. What I want from a money library is the ability to represent this tuple and perform exact calculations. Having the ability to represent "money in hand" is something else, which is less relevant to me, but I can see the use-case for that. Rounding and distribution would be relevant to such a type indeed. Regarding having special types to reflect price could be interesting. But what would that mean, do I have to multiply the Price by unit and get the (rounded) Amount? ( |
I was looking up a library for For instance, in the stock exchange domain, you can't round anything. You can surely show in the screen rounded numbers, but in your domain/database/code you can't, otherwise investors will lose money. Investors can buy fractions of stocks, for example, one can buy I've created a FinancialDecimal
Usage
|
Money has strict rules in the real world. For Euro, the lowest is one cent. This isn't a "money in hand" rule. You just can't represent money lower than one cent. You can do a calculation, but in the end, it needs to be rounded, if you want to represent it in money.
A price is a ratio, which can be allowed to have a higher precision (same as the ExchangeRate in this lib). When calculating a line-item total, the result should be rounded. Why do you think you need the non-rounded amounts? I'm very hesitant to add an unrounded money type, which goes against accounting rules, if there is not a good use case. If you need the intermediate result and want to store it, the decimal type is perfect for that. You can still create a tuple if you need to keep the currency close to the calculated value:
That is an extremely specific and complex domain with his own rules. It could be that this library is not a suitable candidate for that domain.
I don't understand what this solves. Just use decimal in your domain, it's the perfect type for it. If you want to have a handy method to Btw with this lib, you can also do:
|
By default the Money instance will round every math operation (+. -, *. /, etc) that might have fractions of cents using a RoundingMode.
This will cause issues with multiple operations and money loss. If you multiple by a couple of numbers, do some division, add a few things, do some more division, you'll end up losing multiple cents along the way. This happens frequently in financial applications and many others as well.
There needs to be a way to perform multiple calculations on a Money instance without any rounding and then round at the end. Perhaps a MoneyCalculater (or building pattern) of some sort that can provide multiple operations without rounding and in the end provide back a Money instance that is rounded.
The best is probably to use a delegate like
Func<Money, Money>
,Action<Money>
or a custom delegate.See an example like https://github.com/gigs2go/joda-money-calculator/tree/master/joda-money-calculator or the builder pattern in JavaMoney.
The text was updated successfully, but these errors were encountered: