Java money and ratios, how to store and manipulate? - java

I've been trying to refactor a real time application that uses BigDecimal, to a fixed point representation with 10 decimal digits using a long, as it's what is commonly advised to store and manipulate money in Java. The problem is I need to deal with ratios as well (exchange rates, discount rates, percentages, etc) and I'm not sure how to do that in a sane way.
So to create those decimals I do this (encapsulated in a class of course):
long multiplier = (long) Math.pow(10, precision)
long decimal = 1.2345 * multiplier; // 12345000000
Here are some basic operations with this representation:
long addition = decimal1 + decimal2;
long subtraction = decimal1 - decimal2;
long multiplication = (decimal1 * decimal2) / multiplier;
long division = (decimal1 * multiplier) / decimal2;
As you can see, multiplications and divisions written like that are prone to overflows even for small numbers. That reduces the range of valid arguments for these operations greatly.
So some may ask why do you need that in the first place? Well, to convert from a currency to another for instance: eur=usd*rate, or to get the return of an investment: roi=profit/investment.
What is the real way to go to get good accuracy up to a certain number of digits and excellent performance (as close as possible to using primitive types)?

Use primitive types and store cents (not dollars/euros/whatever)!
That’s what banks do, and what you should do too.
Putting a decimal point in a number is a rendering issue; don’t let rendering influence your core data type.
A long of cents will accurately hold the total global debt thousands of times over - it’s big enough to hold any value you will ever encounter.

Your question doesn't make sense.
You only multiply money by scalars. It is meaningless to multiply two money amounts together. What exactly are you expecting? Square money?
The result of dividing a money amount by another is not a money amount. It is a ratio, e.g. an exchange rate.
NB a long holds a lot more than 10 decimal digits. More like 20 offhand.

Related

BigDecimal and scaling for prices

I am trying to figure out what is the best way to have two decimal scaling for prices.
So here is the scenario. Lets say I have full price of 100.00 and after discount you pay 90. So the discount percent is 10%. To achieve this I wrote something like, which works fine
BigDecimal grossPrice = new BigDecimal(100);
BigDecimal discountedPrice = new BigDecimal(90);
BigDecimal.ONE.subtract(discountedPrice.divide(grossPrice,2, RoundingMode.HALF_EVEN))
.multiply(BigDecimal.valueOf(100))
.setScale(2, RoundingMode.HALF_EVEN)
.doubleValue();
But as soon as I change discountedPrice to 89.5, and I expect discount percent to 10.5, but I still get 10 and the reason is clear because 89.5/100 gives 0.895 and since its half even rounds it to 0.9 and so still 10%
If i do HALF_UP, its as good as half_even. If i do, HALF_DOWN, the value will be 0.89 and I will have discount percent as 11. So I am bit confuse as to what will actually give me 10.5% discount in this case.
How about setting scale to 3. Remember to change it to both places:
At where you divide,
At where you round to half even.
BigDecimal grossPrice = new BigDecimal(100);
BigDecimal discountedPrice = new BigDecimal("89.5");
double doubleValue = BigDecimal.ONE
.subtract(discountedPrice.divide(grossPrice,3, RoundingMode.HALF_EVEN)) // here
.multiply(BigDecimal.valueOf(100))
.setScale(3, RoundingMode.HALF_EVEN) // here
.doubleValue();
System.out.println(doubleValue); // 10.5
You might want to define MathContext to support your calculations and avoid typos:
MathContext halfEvenTo3 = new MathContext(3, RoundingMode.HALF_EVEN);
BigDecimal grossPrice = new BigDecimal(100);
BigDecimal discountedPrice = new BigDecimal("89.5");
double doubleValue = BigDecimal.ONE
.subtract(discountedPrice.divide(grossPrice, halfEvenTo3)) // here
.multiply(BigDecimal.valueOf(100))
.round(halfEvenTo3) // here
.doubleValue();
System.out.println(doubleValue); // 10.5
Given your rounding mode, it seems clear that you intend for the system to never end up at fractions of cents. A good plan; most financial systems cannot deal with fractional cents (you can't transfer half of a cent, or pay half a cent at the register, or even enter half a cent in a POS tool).
That means you can ditch BigDecimal entirely, and use plain jane long, which will then represent the number of 'atomic currency units'. That'd be yen for yen, cents for euros and dollars, pennies for pounds, satoshis for bitcoin, etcetera.
The numbers you are trying to represent here are completely different, and it's good to go back to what things mean, first, and then write code.
In your code, grossPrice and discountedPrice are both monetary amounts.
On the other hand, the value you are looking for isn't a monetary amount at all: It's a completely different thing - a ratio. This also shows in what you desire: For the monetary amounts, you wish to round to 2 decimals at all times, but for the ratio you don't, which is sensible - they are 2 completely different concepts.
Ratios are tricky. For example, the ratio between 1 and 3 is not perfectly representable in either base 10 (0.333333) or the base2 that e.g. double and co use. Therefore, it is not possible as a general rule to have perfect ratios. You must therefore kiss that goodbye and pick, instead, some arbitrary precision.
One easy way out is to just say: Well, hey, if perfection is no longer on the table, I'll make sure my code and documentation is written to keep that in mind, and I don't need it anymore. In which case, you might as well go with double. double is a horrible idea to represent monetary amounts with, but ratios - that's fine.. and in your code you go to double anyway.
In other words, stop using BigDecimal here: You are using it for 2 things, and in both cases, there wasn't an actual point:
You use it to represent monetary amounts, but in a way that fractional atomic units are impossible. You can do that, but it's overengineered and needlessly complex, just use a long to store those atomic units instead.
You use it to calculate a ratio, but you convert it to a double, so whatever fine-grained precision control you wanted to apply is undone by your conversion.
long grossPrice = 10000; // $100
long discountedPrice = 8950; // $89.50
double ratio = 100.0 * discountedPrice / grossPrice;
When printing these numbers, that's where you bring the decimals in:
System.out.printf("Ratio: %.3f\n", ratio); // print to 3 decimal places.
If you do need the fine grained control, or you want fractional atomic finance units, then please keep in mind:
In many ways 'fractional atomic finance' is unsolvable. For example, if you want to split a 4-cent surcharge across 3 people, you just can't do that, and no amount of BigDecimal is going to help you here: BigDecimal is incapable of perfectly representing '1 1/3 of a cent', and even if somehow it could, you can't actually charge that. The best solution would be to either round up (charge each member 2 cents), or to flip a coin, and charge 2 people 1 cent, and randomly charge one person 2 cents. There is no way to do this automatically, you'd have to code it up. In general, once division or ratios are involved, perfection is off the table.
There are libraries for currencies, such as joda-currency. You may want to use those instead, if your aim is 'convenience' (BigDecimal isn't very convenient, hence why 'I'll use BD instead of longs; more convenient' is a weird conclsuion).
longs CAN overflow/underflow, but that isn't relevant unless you're talking about centuries's worth of world economic output. You need a lot of money to overflow the model of 'store cents in a long'.
int however, that's no good: You can easily overflow those (40 million bucks is all you need!), so make sure to use longs.

Java - How to reduce float number precision? [duplicate]

This question already has answers here:
Java float 123.129456 to 123.12 without rounding
(5 answers)
How to round a number to n decimal places in Java
(39 answers)
Closed 5 years ago.
Can I reduce the precision of a float number?
In all the searching I've been doing I saw only how to reduce the precision for printing the number. I do not need to print it.
I want, for example, to convert 13.2836 to 13.28. Without even rounding it.
Is it possible?
The suggested answer from the system is not what I am looking for. It also deals with printing the value and I want to have a float.
There isn't really a way to do it, with good reason. While john16384's answer alludes to this, his answer doesn't make the problem clear... so probably you'll try it, it won't do what you want, and perhaps you still won't know why...
The problem is that while we think in decimal and expect that the decimal point is controlled by a power-of-10 exponent, typical floating point implementations (including Java float) use a power-of-2 exponent. Why does it matter?
You know that to represent 1/3 in decimal you'd say 0.3(repeating) - so if you have a limited number of decimal digits, you can't really represent 1/3. When the exponent is 2 instead of 10, you can't really represent 1/5 either, or a lot of other numbers that you could represent exactly in decimal.
As it happens .28 is one of those numbers. So you could multiply by 100, pass the result to floor, and divide by 100, but when this gets converted back to a float, the resulting value will be a little different from .28 and so, if you then check its value, you'll still see more than 2 decimal places.
The solution would be to use something like BigDecimal that can exactly represent decimal values of a given precision.
The standard warnings about doing precision arithmetic with floats applies, but you can do this:
float f = 13.2836;
f = Math.floor(f * 100) / 100;
if you need to save memory in some part of your calculation, And your numbers are smaller than 2^15/100 (range short), you can do the following.
Part of this taken from this post https://stackoverflow.com/a/25201407/7256243.
float number = 1.2345667f;
number= (short)(100*number);
number=(float)(number/100);
You only need to rememeber that the short's are 100 times larger.
Most answers went straight to how do represent floats more accurately, which is strange because you're asking:
Can I reduce the precision of a float number
Which is the exact opposite. So I'll try to answer this.
However there are several way to "reduce precision":
Reduce precision to gain performance
Reduce memory footprint
Round / floor arbitrarily
Make the number more "fuzzy"
Reduce the number of digits after the coma
I'll tackle those separately.
Reduce precision to gain performance
Just to get it out of the way: simply because you're dropping precision off of your calculations on a float, doesn't mean it'll be any faster. Quite the contrary. This answer by #john16384:
f = Math.floor(f * 100) / 100;
Only adds up computation time. If you know the number of significant digits from the result is low, don't bother removing them, just carry that information with the number:
public class Number WithSignificantDigits {
private float value;
private int significantdigits;
(implement basic operations here, but don't floor/round anywhere)
}
If you're doing this because you're worried about performance: stop it now, just use the full precision. If not, read on.
Reduce memory footprint
To actually store a number with less precision, you need to move away from float.
One such representation is using an int with a fixed point convention (i.e. the last 2 digits are past the coma).
If you're trying to save on storage space, do this. If not, read on.
Round / floor arbitrarily
To keep using float, but drop its precision, several options exist:
#john16384 proposed:
`f = Math.floor(f * 100) / 100;`
Or even
f = ((int) (f*100)) / 100.;
If the answer is this, your question is a duplicate. If not, read on.
Make the number more "fuzzy"
Since you just want to lose precision, but haven't stated how much, you could do with bitwise shifts:
float v = 0;
int bits = Float.floatToIntBits(v);
bits = bits >> 7; // Precision lost here
float truncated = Float.intBitsToFloat(bits);
Use 7 bitshifts to reduce precision to nearest 1/128th (close enough to 1/100)
Use 10 bitshifts to reduce precision to nearest 1/1024th (close enough to 1/1000)
I haven't tested performance of those, but If your read this, you did not care.
If you want to lose precision, and you don't care about formatting (numbers may stil have a large number of digits after the coma, like 0,9765625 instead of 1), do this. If you care about formatting and want a limited number of digits after the coma, read on.
Reduce the number of digits after the coma
For this you can:
Follow #Mark Adelsberger's suggestion of BigDecimals, or
Store as a String (yuk)
Because floats or doubles won't let you do this in most cases.

Java big decimal or double

I have a number in the format of (13,2) 13 digits and 2 decimal places. I need to do some calculation on it (like multiplication, division). I am planning to use BigDecimal for the calculations. Shall i use double or float for the calculation as BigDecimal is bit on slower side?
The most important consideration is not speed but correctness.
If your value is a sample of a continuous value, like a measurement of a real-world property like size, distance, weight, angle, etc., then an IEEE-754 double or float is probably a better choice. This is because in this case powers of ten are not necessarily "rounder" than other values (e.g. angular measurements in radians can be transcendental numbers but still "round").
If your value is a discrete value like a measurement of money, then double is incorrect and a floating-point decimal type like BigDecimal is correct. This is because, in this case, discrete increments are meaningful, and a value of "0.01" is "rounder" and more correct than a number like "0.009999999999999" or "0.010000000000000001".
The simplest, most natural representation for data with two decimal places is BigDecimal with scale factor 2. Start with that. In most cases it will be fast enough.
If, when measured, it really is a serious performance problem, there are two more options:
Use long to represent the number of hundredths. For example, US currency can be represented exactly as a long number of cents. Be very careful to ensure variable names and comments make it clear where dollars are being used, and where cents are being used.
Use double to represent the amount as a fraction. This avoids the dollars-vs-cents bookkeeping, at the expense of rounding issues. You may need to periodically correct the rounding by multiplying by 100, rounding to nearest integer, and dividing by 100 again

What is the right data type for calculations in Java

Should we use double or BigDecimal for calculations in Java?
How much is the overhead in terms of performance for BigDecimal as compared to double?
For a serious financial application BigDecimal is a must.
Depends on how many digits you need you can go with a long and a decimal factor for visualization.
For general floating point calculations, you should use double. If you are absolutely sure that you really do need arbitrary precision arithmetic (most applications don't), then you can consider BigDecimal.
You will find that double will significantly outperform BigDecimal (not to mention being easier to work with) for any application where double is sufficient precision.
Update: You commented on another answer that you want to use this for a finance related application. This is one of the areas where you actually should consider using BigDecimal, otherwise you may get unexpected rounding effects from double calculations. Also, double values have limited precision, and you won't be able to accurately keep track of pennies at the same time as millions of dollars.
How much is the overhead in terms of performance for BigDecimal as compared to double?
A lot. For example, a multiplication of two doubles is a single machine instruction. Multiplying two BigDecimals is probably a minimum of 50 machine instructions, and has complexity of O(N * M) where M and N are the number of bytes used to represent the two numbers.
However, if your application requires the calculation to be "decimally correct", then you need to accept the overhead.
However (#2) ... even BigDecimal can't do this calculation with real number accuracy:
1/3 + 1/3 + 1/3 -> ?
To do that computation precisely you would need to implement a Rational type; i.e. a pair of BigInteger values ... and some thing to reduce the common factors.
However (#3) ... even a hypothetical Rational type won't give you a precise numeric representation for (say) Pi.
As always: it depends.
If you need the precision (even for "small" numbers, when representing amounts for example) go with BigDecimal.
In some scientific applications, double may be a better choice.
Even in finance we can't answer without knowing what area. For instance if you were doing currency conversions of $billions, where the conversion rate could be to 5 d.p. you might have problems with double. Whereas for simply adding and subtracting balances you'd be fine.
If you don't need to work in fractions of a cent/penny, maybe an integral type might be more appropriate, again it depends on the size of numbers involved.

How do I round up currency values in Java?

Okay, here's my problem.
Basically I have this problem.
I have a number like .53999999.
How do I round it up to 54 without using any of the Math functions?
I'm guessing I have to multiply by 100 to scale it, then divide?
Something like that?
The issue is with money. let's say I have $50.5399999 I know how to get the $50, but I don't have how to get the cents. I can get the .539999 part, but I don't know how to get it to 54 cents.
I would use something like:
BigDecimal result = new BigDecimal("50.5399999").setScale(2, BigDecimal.ROUND_HALF_UP);
There is a great article called Make cents with BigDecimal on JavaWorld that you should take a look at.
You should use a decimal or currency type to represent money, not floating point.
Math with money is more complex than most engineers think (over generalization)
If you are doing currency calculations, I think you may be delving into problems that seem simple at their surface but are actually quite complex. For instance, rounding methods that are a result of business logic decisions that are repeated often can drastically affect the totals of calculations.
I would recommend looking at the Java Currency class for currency formatting.
Also having a look at this page on representing money in java may be helpful.
If this is homework, showing the teacher that you have thought through the real-world problem rather than just slung a bunch of code together that "works" - will surely be more impressive.
On a side note, I initially was going to suggest looking at the implementation of the Java math methods in the source code, so I took a look. I noticed that Java was using native methods for its rounding methods - just like it should.
However, a look at BigDecimal shows that there is Java source available for rounding in Java. So rather than just give you the code for your homework, I suggest that you look at the BigDecimal private method doRound(MathContext mc) in the Java source.
If 50.54 isn't representable in double precision, then rounding won't help.
If you're trying to convert 50.53999999 to a whole number of dollars and a whole number of cents, do the following:
double d = 50.539999; // or however many 9's, it doesn't matter
int dollars = (int)d;
double frac = d - dollars;
int cents = (int)((frac * 100) + 0.5);
Note that the addition of 0.5 in that last step is to round to the nearest whole number of cents. If you always want it to round up, change that to add 0.9999999 instead of 0.5.
Why would you not want to use any Math functions?
static long round(double a)
-Returns the closest long to the argument.
http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Math.html
To represent money I would take the following advice instead of re-inventing the wheel:
http://www.javapractices.com/topic/TopicAction.do?Id=13
Try storing your currency as number of cents (you could abstract this to number of base curreny units) with a long.
Edit: Since this is homework, you may not have control over the types. Consider this a lesson for future projects
long money = 5054;
long cents = money % 100;
long dollars = money / 100; // this works due to integer/long truncation
System.out.printf("$%d.%02.d", dollars, cents);
You need to make the number .535 and compare that with your original number to see if you'll round up or down. Here's how you get .535 from .53999999 (should work for any number):
num = .53999999;
int_num = (int)(num * 100); // cast to integer, however you do it in Java
compare_num = (int_num + 0.5) / 100;
compare_num would be .535 in this case. If num is greater than or equal to compare_num, round up to int_num + 1. Otherwise round down simply to int_num.
Sean seems to have it, except, if you want to impose proper rules then you may want to throw in an if statement like so:
double value = .539999;
int result = (int) (value*100);
if(((value*100)%result)>.5)
result++;
I suggest you use long for rounding a double value. It won't matter for small numbers but could make a difference.
double d = 50.539999;
long cents = (long)(d * 100 + 0.5);
double rounded = cents/100;
What exactly are you trying to do? Are you always trying to go to two digits? Or are you always trying to fix things like 99999 at the end no matter what?
According to the comments, it is in fact the former: rounding to dollars and cents. So indeed just round(100*a)/100 is what you want. (The latter would be much more complicated...)
Finally, if you just want to extract the cents part, the following would work:
dollars_and_cents = round(100*a)/100
cents = (dollars_and_cents-(int)dollars_and_cents)*100
(or does java just have frac? In which case the last line would just be frac(dollars_and_cents)*100.

Categories