Which are the problems that MoneyJar attempts to solve?
Financial calculations have mathematical and legal problems concerning calculations. Floating point accuracy, rounding, divisions and multiplications with other numbers may cause problems. Taxes are subject to rounding algorithm choices, decimal precision requirements, "sum first then round" or "round first then sum" ordering, etc.
When you use 'float' or 'double' to represent money, the problem is that the computer can only approximate the decimal part. The following shows examples of how this may impact results after only a single operation with non-complicated values. Assume the storage is in double:
Example 1: 0.70 x 1.05 = 0.73499999999999998667732370449812151491641998291015625
Example 2: 1.30 x 1.05 = 1.3650000000000002131628207280300557613372802734375
With the calculation of 5% added to a simple value, we see that in example 1, the rounding used would be downwards (if regular rounding methods are used). In example 2, the amount would always be rounded upwards, but if bankers rounding is used, it should have been rounded downwards.
So, already for single operations there can be deviations up to a cent / penny. Consider the impact for a complete invoice or a complete invoicing run of a company. In a month, this may theoretically have an impact of $500 - $12,000 for one month only, assuming 5,000,000 customers and about $6 - $10 invoice totals with 10 line items.
The reason why this occurs is due to the way how floating point and doubles are stored in memory. The floating point has 32-bits dedicated to a certain number, the first bit being the sign. Another 8 bits are used to store the component. The remaining 23 bits are used to store the fraction.
The decimal part of a floating point can be thought of as the addition of 23 different fractions. (1/2,1/4,1/8,1/16,1/32,1/64,/128,1/256,1/512, ->23 in total). Whether to include the fraction is determined by the 1/ above the division, otherwise it will be 0/. When the range is completed, a binary number is the result.
A few example in this place:
2.5 = 00000001.10000000000000000000000 = 2 + 1/2 + 0/4 + 0/8 + 0/16 .. etc.
118.625 = 1110110.10100000000000000000000 = 2 + 1/2 + 0/4 + 1/8 + 0/16 .. etc.
4.25 = 00000011.01000000000000000000000 = 4 + 0/2 + 1/4 + 0/8 + 0/16 .. etc.
This is why certain decimals are an approximation, but not exact. The double type has 52 of these fractions available to better approximate the actual value, but it still is not exact enough in various cases, as the above illustrates.
These problems are sometimes reduced in native C and Java code by using a technique that multiplies the currency amount with 100 or 1000. This only moves the floating point by 3 places, reducing the impact of the accuracy problem, but not solving it. Very small currency amounts with a high number of items can still cause large differences. It is an acceptable approach for some systems, but not all.
MoneyJar uses a different method for the calculation of money which does not rely on floating point, but on integer math. Integer math does not suffer from inaccuracies in this way (although it has limits to the values that can be represented). The Java library uses the class BigDecimal for these calculations. The drawback is that calculations themselves are less efficient and take longer, but the results are very precise and accurate.
There are not many libraries around that can calculate subscription charges. For example, if you have an application that needs to charge for 3-months of usage, or 1-yearly or 1-weekly subscriptions, MoneyJar has functionality that can do this.
How the problems are solved
The BigDecimal class of Java can very accurately calculate numbers up to very high amounts. This class internally uses integer math. If you wish to know more about specifics, it is worth to have a look at that BigDecimal class in the Java API documentation.
The Money library exposes just the methods that make sense on Money (BigDecimal was created to be useful for scientific applications too). The Money class contextualizes the figure stored in BigDecimal. That is, it associates a default precision to the monetary amount, it associates a default rounding method and it stores the currency of the amount. For rounding, it is also possible to pass in a custom rounding method or precision of course. All this is to help the developer to protect against calculations of money that are not of the same currency, rounding or precision and improves the consistency of the application.
The default decimal accuracy is taken from the currency class. This currency class by default initializes to the currency of the default locale (JVM locale). So, if you live in Japan, the number of decimals used is normally 0 (Yen). But if you live in the US, the number of decimals is normally 2.
MoneyJar can calculate tax on any money amount. But it also correctly works with different "tax" settings that may exist for different line items on the same invoice. For example, some articles in the UK are VAT - exempt ( it is a luxury tax ). These articles have a different "tax" amount applied ( 0.00% ) against the default VAT tax of 17.5%. In MoneyJar, you can configure a so-called "TaxModel", which is a model that applies to a particular customer. (some customers are totally tax-exempt for example). This TaxModel contains all the rules and taxes that apply to the particular customer and line items.
The TaxModel is not a simple single tax. You can configure there, per tax, the rounding method and the rounding precision, plus the order in which the taxes for that model are applied. You can even specify whether tax should be calculated over other taxes previously applied added to the total. (the order decides which taxes are calculated first).
The AbstractInvoice class is a default invoice class that puts this all together. You typically apply a particular TaxModel to the invoice, add an invoice number, then add all the invoice lines. In this process, the library calculates the tax due at the line level. It also adds the prices for each invoice line together, so that "tax buckets" are developed, which are available at the end of the invoicing process.
Tax Buckets are used for certain financial requirements that tax is calculated over the sum of all prices that apply to a certain tax structure. For example, if you have 50 lines for tax I and 20 lines for tax II, you should sum all the prices for tax I and then apply the tax on that amount to determine the tax due. It would in most cases be financially incorrect to use the calculated rounded tax that is due per line (summing up the individual "line" taxes ). Tax Buckets are already maintained by the MoneyJar library.
Finally, the "FinancialCalendar" class in MoneyJar is a useful utility class that helps in the raw calculation of subscription/billing periods and pro-rata charges. More helpful features will be added in the future that makes its usage even easier.