Basically the problem is the following: given a set of (positive) floating point numbers, which sum up to x, how do I rescale them in the range [0, 1] so that this new set, when multiplied with x, gives the original set, up to the second decimal place? The max x I expect to encounter is probably in the order of 10000000000, just to be sure.
I've run a quick property based test, and the results are precise only if I use BigDecimal with a MathContext with precision of 8, rounding half up.
Is there some mathematical proof of how much precision I need for this problem?
Edit: sorry the precision was 14, 8 was for a later test with smaller amounts
Something like: ------
class MathOps{
public MathOps(RoundingMode mode, int scale) {
this.mode = mode;
this.scale = scale;
}
....
public BigDecimal add(BigDecimal x, BigDecimal y){
return x.add(y).setScale(scale, roundingMode);
}
....
public BigDecimal divide(BigDecimal num, BigDecimal denom) {
return num.divide(denom, scale, roundingMode).setScale(scale, roundingMode);
}
...
}
-----
You can then either create a new instance when you need to perform a math operation or just create a singleton instance if scale and rounding mode are always the same.For financial calculations in US, I would recommend using RoundingMode.HALF_EVEN. From the docs "Rounding mode to round towards the "nearest neighbor" unless both neighbors are equidistant, in which case, round towards the even neighbor. Behaves as for RoundingMode.HALF_UP if the digit to the left of the discarded fraction is odd; behaves as for RoundingMode.HALF_DOWN if it's even. Note that this is the rounding mode that statistically minimizes cumulative error when applied repeatedly over a sequence of calculations. It is sometimes known as "Banker's rounding," and is chiefly used in the USA. "
- amount: expressed in the smallest subdivision of the currency, as an integer.
- currency: unique code, base, and exponent
- scale: the scale is essential to accurately represent monetary values without losing precision. It automatically adapts as needed to ensure you always retain accurate amounts.
---
Multiply[1]: If you need to multiply by a fractional multiplier, you shouldn't use floats, but scaled amounts instead. For example, instead of passing 2.1, you should pass { amount: 21, scale: 1 }. When using scaled amounts, the function converts the returned objects to the safest scale.
Allocate[2] (alternative to dividing): Distribute the amount of a Dinero object across a list of ratios.
https://docs.oracle.com/javase/7/docs/api/java/math/MathCont...
I save financial data in cents (or whatever the lowest is), and only show it as a formatted number in templates. Stripe and other payment providers seem to do the same.
But more importantly, what exactly are you trying to do? It sounds like you want to convert floats to percents and back again to get the same exact values. Double or BigDecimal should be fine to get back to the second decimal place in your scenario. I don't have a proof for that, but you could generate tests and data to test your system. If you're really concerned about getting the original values back, the. You can create an audit table to preserve them, depending on how this scenario is applied in real life.
(a/3 ) * 3 + (b/3 ) * 3 + (c/3 ) * 3 is not going to be same as a+b+c. (the * 3 , then summing-up parts, can happen years later after the a/3 parts)
So financialy, the last thing in a sum is calculated as remainder. Not as additive. e.g. the (c/3) *3 is "calculated" as a+b+c - the-other-two. i.e. 33+33+33... doesnt do 100. so the last 33 is actualy 100-33-33 = 34. Which is technicaly incorrect - but .. that's life.
Now, which one to choose to be the victim.. there are variants..