BigDecimal scale for large numbers - java

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

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 tell if a result is decimal?

I'm having two float numbers, they can be decimals or not, depending on the operation I want to print the result either with decimal or without.
I'm using String.format to take off the decimal when not needed. However, I have trouble identifying when the result will be decimal. Tried the n1 % n2 > 0 method from a similar question, but when entering for example 6 + 7 the result becomes 13.0 instead of just 13.
Code so far:
float n1 = Float.parseFloat(request.getParameter("num1").toString().trim());
float n2 = Float.parseFloat(request.getParameter("num2").toString().trim());
String res = String.valueOf(n1+n2);
if(n1 % n2 > 0)
{
out.println("It's decimal");
out.println(n1+n2);
}else{
out.println(String.format("%.0f", n1+n2));
}
For doing exact math with non-integers (numbers with fractions) in Java you should use BigDecimal. This class allows for exact representation of non-integers of arbitrary precision.
That is your code should be changed to:
final BigDecimal n1 = new BigDecimal(request.getParameter("num1").toString().trim());
final BigDecimal n2 = new BigDecimal(request.getParameter("num2").toString().trim());
final BigDecimal res = n1.add(n2);
final BigDecimal remainder = n1.remainder(n2);
//if (res.stripTrailingZeros().scale() > 0) {
if (remainder.compareTo(BigDecimal.ZERO) != 0) {
System.out.println("It's decimal");
System.out.println(res);
} else {
final DecimalFormat df = new DecimalFormat("#.0f");
System.out.println(df.format(res));
}
The other answer will not give you the correct result, if a user inputs a number larger than Integer.MAX_VALUE or lower than Integer.MIN_VALUE. Casting to long will probably yield wrong results due to the precision of float that might cause the result to have a decimal fraction even if the input numbers did not...
However I hope you're doing some input validation of the request data. Otherwise you'll most likely get a lot of NumberFormatExceptions from your JSP-page.
UPDATE:
Updated the code example to check for scale instead of precision.
You can use Math.round to tell if a float is an integer. (There are other approaches if your only purpose is to suppress the decimal point for integers when formatting as a string--see KevinO's comment.)
If f is a float, Math.round(f) returns an int, which is f rounded to the nearest integer, unless f is outside the range of an int. However, if f is outside the range of an int, then f must be an integer, for all practical purposes--if f is large enough that it is too big to fit in an int, then it's too big to distinguish between values that are less than 1.0 apart (there are fewer bits of precision in a float than in an int). So there's no way for f to represent a non-integer of that magnitude.
Given that, and assuming that f isn't +/- infinity, you can test whether f is an integer like this:
public boolean isInteger(float f) {
return f > (float)Integer.MAX_VALUE ||
f < (float)Integer.MIN_VALUE ||
f == (float)Math.round(f);
}
But please note that even a non-integer could appear as something.000000 when you format it, since the formatter will have to round f to a certain number of decimal places when printing. It depends on what kind of format you're using.
NOTE: The above method is a correct way to determine whether a float is an integer, if the float is the only information you have. However, in the context of a larger program, there are other considerations. If the parameter string is "2000000000.1", when you parse it as a float you will get 2000000000, which is an integer, because the float doesn't have enough precision to represent the .1. At that point, your float will be an integer value, and it's too late for it to know about the .1--that information has been lost. If you don't want to lose that information, then don't use float or double--use BigDecimal instead.
Cast to int then back:
boolean isDecimal = myFloat != (float)(int)myFloat;
Casting to int truncates the decimal part.
Or, you can use a string approach. Decimals have non-zero digits after the dot, so:
boolean isDecimal = String.valueOf(myFloat).matches(".*\\.(?=.*[1-9]).*");
If you just need that for output, the easiest approach is probably to do this with string manipulation:
String s = String.format("%.4f", n1 + n2); // use whatever decimal places you like
s = s.replaceFirst("\\.0*$", "");
System.out.println(s);
This will fail if the decimal separator in your Locale is , instead of ., so be careful.
To really check if a double value x is integral you can use this test:
if (x % 1 == 0) {
// integral
} else {
// not integral
}

Math in Java when precision is lost

The below algorithm works to identify a factor of a small number but fails completely when using a large one such as 7534534523.0
double result = 7; // 7534534523.0;
double divisor = 1;
for (int i = 2; i < result; i++){
double r = result / (double)i;
if (Math.floor(r) == r){
divisor = i;
break;
}
}
System.out.println(result + "/" + divisor + "=" + (result/divisor));
The number 7534534523.0 divided by 2 on a calculator can give a decimal part or round it (losing the 0.5). How can I perform such a check on large numbers? Do I have to use BigDecimal for this? Or is there another way?
If your goal is to represent a number with exactly n significant figures to the right of the decimal, BigDecimal is the class to use.
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).
Additionally, you can have a better control over scale manipulation, rounding and format conversion.
I don't see what the problem is in your code. It works exactly like it should.
When I run your code I get this output:
7.534534523E9/77359.0=97397.0
That may have confused you, but its perfectly fine. It's just using scientific notation, but there is nothing wrong with that.
7.534534523E9 = 7.534534523 * 109 = 7,534,534,523
If you want to see it in normal notation, you can use System.out.format to print the result:
System.out.format("%.0f/%.0f=%.0f\n", result, divisor, result / divisor);
Shows:
7534534523/77359=97397
But you don't need double or BigDecimal to check if a number is divisible by another number. You can use the modulo operator on integral types to check if one number is divisible by another. As long as your numbers fit in a long, this works, otherwise you can move on to a BigInteger:
long result = 7534534523L;
long divisor = 1;
for (int i = 2; i < result; i++) {
if (result % i == 0) {
divisor = i;
break;
}
}
System.out.println(result + "/" + divisor + "=" + (result / divisor));
BigDecimal is the way to move ahead for preserving high precision in numbers.
DO NOT do not use constructor BigDecimal(double val) as the rounding is performed and the output is not always same. The same is mentioned in the implementation as well. According to it:
The results of this constructor can be somewhat unpredictable. One might assume that writing new BigDecimal(0.1) in Java creates a BigDecimal which is exactly equal to 0.1 (an unscaled value of 1, with a scale of 1), but it is actually equal to 0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the value that is being passed in to the constructor is not exactly equal to 0.1, appearances notwithstanding.
ALWAYS try to use constructor BigDecimal(String val) as it preserves precision and gives same output each time.

In Java, how to achieve this type of number rounding?

I am writing a small physics app. What I am planning to do is to make number rounding. The issue is that it is not a fixed rounding, but rather a variable rounding that depends on the value of the decimal digits. I will give an explanation for the issue.
I always need to keep the whole integer part (if any) and the first five decimal digits (if any).
half up rounding is always used.
21.1521421056 becomes 21.15214
34.1521451056 becomes 34.15215
If the result consists of only decimal digits then:
If the first five digits include non zero digits then keep them.
0.52131125 becomes 0.52131
0.21546874 becomes 0.21547
0.00120012 becomes 0.0012
If the first five digits are all zero digits 0.00000 then go down to first five digits that include non zero digits.
0.0000051234 becomes 0.0000051234
0.000000000000120006130031 becomes 0.00000000000012001
I need to play this rounding while working with BigDecimal because it is a requirement for my needs.
I think this will work, based on experimentation, if I understand correctly what you want. If d is a BigDecimal that contains the number:
BigDecimal rounded = d.round(new MathContext
(d.scale() - d.precision() < 5
? d.precision() - d.scale() + 5
: 5));
Is this, what you are looking for?
public static void main(String[] args){
double d = 0.000000000000120006130031;
System.out.println(round(d, 5));
}
private static double round(double d, int precision) {
double factor = Math.pow(10D, precision);
int value = (int)d;
double re = d-value;
if (re * factor <= 0.1 && d != 0) {
while (re * factor <= 0.1) {
factor *= 10;
}
factor *= Math.pow(10D, precision);
}
re = ((int)(re*factor))/factor+value;
return re;
}
(sorry, it's a little quick & dirty, but you can improve it, if you want)
EDIT:
make it <= in the conditions, this should work better

Java, BigDecimal. Problems with division

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...)

Categories

Resources