Java, BigDecimal. Problems with division - java

I'm trying to calculate a percentage "factor". That is, given a 20%, convert it into 0.2 (my intention is to later multiply values by that and get the 20% of the values).
Anyway, the question is related with this piece of code:
public static void main(String[] args) {
int roundingMode = BigDecimal.ROUND_FLOOR;
BigDecimal hundred = new BigDecimal("100");
BigDecimal percentageFactor = null;
BigDecimal percentage = new BigDecimal("20");
BigDecimal value = new BigDecimal("500");
percentageFactor = percentage.divide(hundred, roundingMode);
float f = percentage.floatValue() / hundred.floatValue();
f = value.floatValue() * f;
BigDecimal aux = value.multiply(percentageFactor);
System.out.println("factor:"+percentageFactor.toString());
System.out.println("final falue:"+aux.toString());
System.out.println("Float Value:"+f);
}
I would expect the outcome of this to be something like:
factor: 0.2
final value: 100
float value: 100
but instead percentage.divide(hundred, roundingMode); is returning zero, an hence I get:
factor:0
final falue:0
Float Value:100.0
What am I doing wrong? How can I divide two big decimals properly?
By the way, I'm using BigDecimal because I will be calculating monetary percentages, so I want control regarding rounding.

I think that the best solution is to set the requested scale when dividing: In this case perhaps 2.
var hundred = new BigDecimal(100);
var percentage = new BigDecimal(20);
var value = new BigDecimal(500);
var percentageFactor =
percentage.divide(hundred,2, BigDecimal.ROUND_HALF_UP);
value = value.multiply(percentageFactor);
System.out.println("final value:"+ value);
Final value: 100.00
The multiplication is using the scale from the factors (0+2) but it can be specified too.
I'd use ROUND_HALF_UP for accounting (in my legislation) or ROUND_EVEN (for statistics) for rounding mode.

The scale of new BigDecimal("20") is zero because you've got no decimal point in there. That means that your percentage.divide(hundred, BigDecimal.ROUND_FLOOR) will produce zero (it's effectively int(20/100) or 0).
If you really want to do fractional stuff, use new BigDecimal("20.00") so the scale is set correctly, or use one of the other constructors to set the scale specifically.
Here's the output from that simple change of 20 to 20.00, complete with your spellink misteak :-)
factor:0.20
final falue:100.00
Float Value:100.0

float has only 6 digits of accuracy and is almost never a good choice, I would suggest you use double instead. (or BigDecimal can be better in some cases)

The reason factor is 0 instead of 0.2 in your code is because
you've set the RoundingMode to be FLOOR (which means ROUND DOWN), and
your percentage variable has an implicit scale of 0 (any BigDecimals initialised from round number without specifying scale will have scale of 0)
So when you call divide you are rounding down any decimals and you are maintaining a scale of 0, and hence 0.2 is rounded down to 0.
To get the correct number, you can either
specify the scale explicitly, or
since you know you are dividing against 100, you can just use the BigDecimal#divide(BigDecimal) method instead (without providing scale or RoundingMethod). In your case, the method will not throw ArithmeticException since there is no possibility of non-terminating decimals (think 20 / 100 = 0.2, 20 / 10000 = 0.002 - decimals always terminate when dividing by 100).
However, if you're dividing against another number say 3 then you need to specify the scale because there is a possibility of non-terminating decimals (think 10 / 3 = 3.3333333333...)

Related

Increment floating point numbers with the precision of the input with Java

I am looking for the most optimized and easy to read version of incrementing a floating point number with its own precision:
increment(1000) should return 1001
increment(100.1) should return 100.2
increment(0.1) should return 0.2
increment(0.01) should return 0.02
increment(0.001) should return 0.002
increment(0.0009) should return 0.0010
increment(0.000123) should return 0.000124
increment(increment(0.0009)) should return 0.002
It could be done by string operation but I don't want to convert this to string and parse it back to double.
I have done the following with String operations:
public static double incrementWithMover(double value){
DecimalFormat df = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
df.setMaximumFractionDigits(340); //340 = DecimalFormat.DOUBLE_FRACTION_DIGITS
String valueString = df.format(value);
String[] splitted = valueString.split("\\.");
StringBuilder mover = new StringBuilder();
if(splitted.length == 2){ // Floating Decimals
int precision = splitted[1].length();
df.setMaximumFractionDigits(precision);
mover = new StringBuilder("0.");
for(int i =1; i<precision; i++){
mover.append("0");
}
mover.append("1");
}
else{ // Non Floating Decimals
mover = new StringBuilder("1");
}
double incremented = Double.parseDouble(valueString) + Double.parseDouble(mover.toString());
return Double.parseDouble(df.format(incremented));
}
I am trying to write this method due to I am checking different values and trying to increment all the values in their own precision by one
What could be the best way to write such incrementFloating method?
This may work for you. Changed from doubles to strings.
String[] vals = { "1000","1000.1", ".1", ".01", ".001", ".00123", ".0004" };
for (String v : vals) {
System.out.printf("%s -> %s%n", v, incrementFloating(v));
}
prints
1000 -> 1001
1000.1 -> 1000.2
.1 -> 0.2
.01 -> 0.02
.001 -> 0.002
.00123 -> 0.00124
.0004 -> 0.0005
The method declaration
public static String incrementFloating(String v) {
BigDecimal b = new BigDecimal(v);
BigDecimal increment =
BigDecimal.valueOf(1).scaleByPowerOfTen(-b.scale());
return b.add(increment).stripTrailingZeros().toString();
}
Increment floating point numbers with the precision of the input with Java
This just isn't how it works.
Floats and Doubles aren't stored like you evidently think they are.
Imagine the whole number line. from negative infinity to positive infinity.
This line has an infinite number of integer values on it. Between any 2 integer values, and infinite number of values exist there, too.
Computers aren't magic. Floats are 32-bit, doubles are 64-bit. A 32-bit, by basic math, can only differentiate at most 2^32 numbers, that's about 4 billion.
4 billion is way, way less than 2 orders of infinity.
So how does that work then? Well, there are about 4 billion numbers that are 'blessed'. These numbers are representable by float, and no other numbers are. 0.3, for example, is not blessed. 0.3 is simply not a number in the float numeric system. It doesn't exist.
So, how do I explain that float x = 0.3; works, or what happens when you run float x = 0.1 + 0.2;?
Well, float and double operations convert, silently, to the nearest blessed number.
The distribution of blessed numbers is based on binary (so in decimal they don't make any particular sense), and aren't equally distributed. Near 1.0 there are way more than near 100.0, for example.
That means errors sneak in everywhere. The operation you describe fundamentally doesn't make sense here. You can't do what you want with floats or doubles. Period.
Go with Strings, or go with BigDecimal.
If you're interested, here you go:
BigDecimal bd = new BigDecimal(0.3);
System.out.println(bd);
> 0.299999999999999988897769753748434595763683319091796875
I didn't make that up. Write that code and run it. What is that ungodly number?
That's the nearest blessed number to 0.3.
So, in double number systems, applying your algorithm, increment(0.3) would try to calculate 0.299999999999999988897769753748434595763683319091796876, which isn't blessed, and the nearest blessed number to that is simply 0.299999999999999988897769753748434595763683319091796875 again, and the operation would do nothing.
Makes no sense.
Strings or BigDecimal, it is the only way. Efficiency is in that sense out the window, but unless you intent to run this op a few million times a second, you won't notice.

How to cap a double's precision to a precision given in the form of 0.00...1?

I have this strange issue of having to cap the precision of double numbers to a number of decimal places, or no decimal places at all, where the precision is handed to me like so: 0.001, 0.1, 1, 0.00001, etc.
So I could be given, for example, 1.234247324 and a precision indicator of 0.001, and with that I would need to return 1.234. If Instead I had been handed a precision of 1.0, I would then return 1 (no decimal places), and so forth.
I'm not sure how to go about this. Does anyone has pointers on how to tackle this?
Thank you!
Start with your value, and create a BigDecimal, then round to what you want.
double value = 1.23456; //not actually equal to that, but pretend.
BigDecimal better = new BigDecimal(value);
BigDecimal rounded = better.round(new MathContext(3));
Rounded is now the actual value you want. What can you do with the precision indicator?
BigDecimal precision = new BigDecimal("0.001");
Now you can get the scale of the precision, which you can use for rounding.
System.out.println(precision.scale());
//outputs 3.
That would be similar to using the logarithm.
Use a hybrid approach of the two other answers allows unusual precisions like 0.25 to be specified:
BigDecimal rounded = (x / precision).round(new MathContext(0)) * precision;
Try this code
double x = 1.234247324;
double p = 0.001;
double y = (int)(x * 1 / p) / (1 / p);
Don't really understand what you want to do but this results 1.234.

BigDecimal scale for large numbers

I'm trying to understand the scale for a BigDecimal but it acts weird and I can't understand why. Here's a couple of examples:
Double d = new Double(1000000d);
int scale = new BigDecimal(d.toString()).scale();
The scale in this example will be 1 which is correct to me.
The result of d.toString() is "1000000.0".
Double d = new Double(10000000d);
int scale = new BigDecimal(d.toString)).scale();
The scale in this example will be -6. Can anyone explain why?
The result of d.toString() is "1.0E7".
I thought the number of digits caused this but if I go:
Double d = new Double(11111111d);
int scale = new BigDecimal(d.toString()).scale();
Expected a scale of -8 but suddenly it's 0.
The result of d.toString() is "1.1111111E7".
These different scales make no sense to me after reading the Javadoc of scale():
Returns the scale of this BigDecimal. 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. For example, a scale of -3 means the unscaled value is multiplied by 1000.
I'd very much appreciate an explanation how BigDecimal behaves when the numbers are large.
Thanks in advance!
The scale you got is the number of decimals with some significance:
1000000d -> 1000000.0 -> 0: the numbers at the right of the dot have no significance, the result is 0;
10000000d -> 1.0E7 -> -6: the numbers at the right of the dot have significance, as if you denormalize the power by ten you get the 6 zeros;
11111111d -> 1.1111111E7 -> 0: all the numbers at the right of the dot have significance, denormalizing the power by ten you get more information, so you "can't" normalize the number if you want to keep this information. This way (the number denormalized), you have 0 numbers at the right of the dot.
EDIT
As commented, the first line is wrong, it must be 1000000d -> 1000000.0 -> 1. The reason is that the numbers with exponential have a different behavior (when obtaining the scale) that the formatted numbers.
The value of 1 is due that BigDecimal counts the numbers in the right side of the dot (which in this case is one, a single 0), subtract the numbers to drop (in this case there is one, the single 0) and add the math precision (by default is one) -> result = 1.
You are seeing the behavior your report because you are calling toString() on the decimal provided, which in for some of your examples represents in exponential notation, which is then preserved by BigDecimal when it chooses the scale.
If you provide the the double directly to the BigDecimal constructor you consistently get 0.
new Double(1000000d).toString() //1.0E7
Double d = new Double(1000000d);
int scale = new BigDecimal(d).scale(); //0
Double d = new Double(10000000d);
int scale = new BigDecimal(d).scale(); //0
Double d = new Double(11111111d);
int scale = new BigDecimal(d).scale(); //0
Update:
scale is is not a useful attribute on its own. It must be considered in conjunction with unscaledValue. The represented number is unscaledValue × 10 ^ -scale.
That is,
BigDecimal d = new BigDecimal(1000000d)
BigDecimal e = d.setScale(2)
int dScale = d.scale() //0
int dUnscaled = d.unscaledValue() //1000000
int eScale = e.scale() //2
int eUnscaled = e.unscaledValue() //100000000
Both d and e are a representation of 1000000. However, e preserves there are 2 trailing zeros (zeros after the decimal point).
d.toString() //1000000
e.toString() //1000000.00

inconsistent behavior from BigDecimal and MathContext

I am Seeing some strange behavior from BigDecimal
When I do division using mathContext the output is different than when I do the division by directly providing the scale and rounding mode
Here is an example that I think should provide the same output
public static void main(String...args){
MathContext mc = new MathContext(3,RoundingMode.HALF_UP);
BigDecimal four = new BigDecimal(4);
BigDecimal three = new BigDecimal(3);
System.out.println(four.divide(three,3,RoundingMode.HALF_UP));
System.out.println(four.divide(three,mc));
}
Output:
1.333
1.33
It appears that the scale is treated differently when using MathContext. Or I dont understand when to use which.
The divide method of BigDecimal lets you specify the scale of the result, which loosely speaking is number of decimal places. scale = 3 means that a number will be expressed with 3 decimal places. A negative scale indicates the number of insignificant zeroes at the end of a whole number - so for example to round to the nearest 1000, you can specify scale = -3.
four.divide(three,3,RoundingMode.HALF_UP); // scale = 3, so round to 3 decimal places
But a MathContext is different. It lets you specify precision - that is, the number of significant digits. This is different from scale.
MathContext mc = new MathContext(3,RoundingMode.HALF_UP);
four.divide(three, mc); // precision = 3, so round to 3 significant figures

unexpected results with decimal arithmetic expression

I have some business logic with arithmetic expression and their results are as follows
(10.0 * 20.58)/1.0=205.7999..98
(6.0 * 37.9)/1.0=227.3999..98
(5.0 * 50.0)/1.0=250.0
(10.0 * 37.9)/1.0=379.0
But expected results are
(10.0 * 20.58)/1.0=205.8
(6.0 * 37.9)/1.0=227.4
(5.0 * 50.0)/1.0=250.0
(10.0 * 37.9)/1.0=379.0
I am not clear why we are getting that .999..98 fraction part? Due to that my equals comparison is failing and so business logic. For few cases we are using
amt = (double)Math.round(orderAmt*100000)/100000;
But that is not possible to do the same in each and every place where we have double arithmetic expression.
I want to know why we get such results randomly and is there any possibility to round the results to 5 decimal places instead of rounding every where?
With radix 10 there are some fractions who can't be expressed exactly with a finite number of digits, like for example 1/3 = 0.33333333....
It's the same with radix 2, except that the dividers that produce this kind of results are not the one we are accustomed to, and for example, for 20.58, which is 2058 / 100, it is the case.
Internally, doubles and floats are stored with bits (an not digit), so the exact value of the double or float just can't be stored in the computer's memory. Each time you perform an operation with this value, you get a small shift, because of the approximation, which becomes visible when converting back to decimal format for printing.
It's something you have to pay attention while perfoming computations where precision is important.
So you have two solutions:
Store all your numbers in decimal type and perform all your calculation with it. This will achieve accuracy but for the price of performance.
You can also keep all the calculation with double or float, and format with a fixed number of digits only for printing results.
You could use BigDecimal for roundoff
BigDecimal bd = new BigDecimal((10.0 * 20.58)/1.0) ;
bd = bd.setScale(4, RoundingMode.UP);
use with a static method
public static double round(double value, int digits) {
BigDecimal bd = new BigDecimal(value);
return bd.setScale(digits, RoundingMode.UP).doubleValue();
}
RoundingMode has :
RoundingMode.UP;
RoundingMode.DOWN;
RoundingMode.HALF_DOWN;
RoundingMode.HALF_EVEN;
The basic problem is that
System.out.println(10.0 * 20.58);
prints
205.79999999999998
has a small rounding error due to a representation error in 20.58
You either need to
round the result before comparing.
use a comparision which allows for some error
use BigDecimal (which is over kill in most cases)
use cents instead of dollars i.e. use int or long with fixed precision.
In the last case, the same operation would read
System.out.println(10 * 2058);
prints
20580
where this is 100x the value you need as its fixed precision e.g. cents instead of dollars.
You may want to use double with rounding as below:
double roundingPlaces = 10.0;//use 10.0 for single decimal digit rounding
double a1 = 10.0;
double b1 = 20.58;
double c1 = 1.0;
System.out.println(Math.round((a1*b1*roundingPlaces)/c1)/roundingPlaces);
This prints 205.8.
float fa1 = (float) 10.0;
float fb1 = (float)20.58;
float fc1 = (float)1.0;
System.out.println(fa1*fb1/fc1);
This also prints 205.8
Use float instead of double
http://ideone.com/L9vwR8
System.out.println((float)((10.0f * 20.58f)/1.0f));
output
205.8

Categories

Resources