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.
Related
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.
Immutable, arbitrary-precision signed decimal numbers. A BigDecimal consists of an arbitrary precision integer unscaled value and a 32-bit integer scale. If zero or positive, the scale is the number of digits to the right of the decimal point. If negative, the unscaled value of the number is multiplied by ten to the power of the negation of the scale. The value of the number represented by the BigDecimal is therefore (unscaledValue × 10-scale).
That is what the doc says. Based on the docs it appears that BigDecimal i useful when
You are dealing with super large numbers
You are concerned about precision
But are there any other scenarios where BigDecimal is a better choice ?
Double is a floating value, meaning that it is not an exact value. Therefore, you need to use BigDecimal which gives you the exact value.
Double will display only 15 significant decimal digits but you can have as many significant digits in BigDecimal as you wish. You can set the value using MathContext class.
BigDecimal is used when you are writing code for developing applications like scientific calculators.
The question maybe has some good sizes to check, for example the financial one is an example, I'll took it from here because I liked it:
Primer on Financial Issues
Currency calculations require precision to a specific degree, such as two digits after the decimal for most currencies. They also require a specific type of rounding behavior, such as always rounding up in the case of taxes.
For example, suppose we have a product which costs 10.00 in a given currency and the local sales tax is 0.0825, or 8.25%. If we work it out on paper, the tax amount is,
10.00 * 0.0825 = 0.825
Because our precision for the currency is two digits after the decimal, we need to round the 0.825 figure. Also, because this is a tax, it is good practice to always round up to the next highest cent. That way when the accounts are balanced at the end of the day, we never find ourselves underpaying taxes.
0.825 -> 0.83
And so the total we charge to the customer is 10.83 in the local currency and pay 0.83 to the tax collector. Note that if we sold 1000 of these, we would have overpaid the collector by this much,
1000 * (0.83 - 0.825) = 5.00
Another important issue is where to do the rounding in a given computation. Suppose we sold Liquid Nitrogen at 0.528361 per liter. A customer comes in and buys 100.00 liters, so we write out the total price,
100.0 * 0.528361 = 52.8361
Because this isn't a tax, we can round this either up or down at our discretion. Suppose we round according to standard rounding rules: If the next significant digit is less than 5, then round down. Otherwise round up. This gives us a figure of 52.84 for the final price.
Now suppose we want to give a promotional discount of 5% off the entire purchase. Do we apply this discount on the 52.8361 figure or the 52.84 figure? What's the difference?
Calculation 1: 52.8361 * 0.95 = 50.194295 = 50.19 Calculation 2:
52.84 * 0.95 = 50.198 = 50.20
Note that we rounded the final figure by using the standard rounding rule.
See how there's a difference of one cent between the two figures? The old code never bothered to consider rounding, so it always did computations as in Calculation 1. But in the new code we always round before applying promotions, taxes, and so on, just like in Calculation 2. This is one of the main reasons for the one cent error.
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
I'm storing a number as a double. It works well with small numbers but if the double is 1000000 and I try to add 10 to it, it doesn't work but I can add 100 to it. If the double is 100000 and I try to add 1 to it, it doesn't work but I can add 10 to it.
Is this a problem with using a double? What format should I be using for up to a 9 digit number?
Edit: I'm doing calculations on the number and putting it in a 0.00 decimal format. It works on small numbers but not on big. It might have something to do with the other calculations. Just seeing if maybe double was the problem.
I run into problems when I take 1000001 / 100 and put in 0.00 decimal format. But 1000100 / 100 put into a 0.00 dec format works.
Not sure what you mean by "it doesn't work", considering that it should:
System.out.println(1000000d + 10d); // 1000010.0
Cases where it wouldn't work are where there is a value that is more than about 15 digits from the most significant digit of the largest double involved:
System.out.println(Math.pow(10, 18) + 10d - Math.pow(10, 18)); // 0.0
System.out.println(Math.pow(10, 17) + 10d - Math.pow(10, 17)); // 16.0
System.out.println(Math.pow(10, 16) + 10d - Math.pow(10, 16)); // 10.0
An int is absolutely large enough to store a 9-digit number, since the maximum value stored by a (signed) integer is 2^31 - 1 = 2,147,483,647 which has 10 digits.
If you need a larger range, use a long.
Java Tutorial: Primitive Data Types
Yes, this is how floating point works in general. (Although, as others pointed out, this should be well within the actual capabilities of double.) It loses more and more precision as the number is getting bigger. And even with small numbers, it isn't guaranteed to be precise. For small integers it is, as a general rule, but in practice, relying on the precision of floating point is a bad idea.
For integers, long should be good enough, 263-1 is the biggest number you can store in it.
Otherwise BigDecimal is the answer. Not very convenient, but it is precise.
Judging by the update to your question, you want to store prices or something similar. In that case, you can either use a library dedicated to currency arithmetic (as there are often weird rules of rounding involved for example), or store pennies/cents in a long.
A double is actually able to hold that result. The most likely thing is probably that you're inspecting/printing out the number in such a way that appears like the number isn't different (could you post your test code?).
As a general rule of thumb, doubles give you ~16 digits of precision, and floats give you ~8 digits of precision.
What kind of 9-digit number do you need to keep? A double should have enough precision track 9 significant digits as would an int, but you need to know which type of number you need before you choose a type. If you need real values (i.e., they have digits to the right of the decimal), then choose double. If they are integers, choose int.
I don't think your exact problem is very clear. Are you using integer arithmetic? In which case you should expect an integer division to round down the answer.
int i = 1000001 / 100;
System.out.println(i);
double d = (double) 1000001 / 100;
System.out.println(d);
prints as expected
10000
10000.01
However, since you have't given a clear example of what does work and what you expect to happen, we are just guessing what your problem might be.
Nevertheless, there is something wrong. It should be possible to (ab)use a double (which is in 64bit IEEE format in Java) as an 48-bit-or so integer. 48 bt should be enough to store 9 decimal digits.
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.