BigDecimal, precision and scale - java

I'm using BigDecimal for my numbers in my application, for example, with JPA. I did a bit of researching about the terms 'precision' and 'scale' but I don't understand what are they exactly.
Can anyone explain me the meaning of 'precision' and 'scale' for a BigDecimal value?
#Column(precision = 11, scale = 2)
Thanks!

A BigDecimal is defined by two values: an arbitrary precision integer and a 32-bit integer scale. The value of the BigDecimal is defined to be .
Precision:
The precision is the number of digits in the unscaled value.
For instance, for the number 123.45, the precision returned is 5.
So, precision indicates the length of the arbitrary precision integer. Here are a few examples of numbers with the same scale, but different precision:
12345 / 100000 = 0.12345 // scale = 5, precision = 5
12340 / 100000 = 0.1234 // scale = 4, precision = 4
1 / 100000 = 0.00001 // scale = 5, precision = 1
In the special case that the number is equal to zero (i.e. 0.000), the precision is always 1.
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. For example, a scale of -3 means the unscaled value is multiplied by 1000.
This means that the integer value of the ‘BigDecimal’ is multiplied by .
Here are a few examples of the same precision, with different scales:
12345 with scale 5 = 0.12345
12345 with scale 4 = 1.2345
…
12345 with scale 0 = 12345
12345 with scale -1 = 123450 †
BigDecimal.toString:
The toString method for a BigDecimal behaves differently based on the scale and precision. (Thanks to #RudyVelthuis for pointing this out.)
If scale == 0, the integer is just printed out, as-is.
If scale < 0, E-Notation is always used (e.g. 5 scale -1 produces "5E+1")
If scale >= 0 and precision - scale -1 >= -6 a plain decimal number is produced (e.g. 10000000 scale 1 produces "1000000.0")
Otherwise, E-notation is used, e.g. 10 scale 8 produces "1.0E-7" since precision - scale -1 equals is less than -6.
More examples:
19/100 = 0.19 // integer=19, scale=2, precision=2
1/1000 = 0.0001 // integer=1, scale = 4, precision = 1

Precision: Total number of significant digits
Scale: Number of digits to the right of the decimal point
See BigDecimal class documentation for details.

Quoting Javadoc:
The precision is the number of digits in the unscaled value.
and
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.

Precision is the total number of significant digits in a number.
Scale is the number of digits to the right of the decimal point.
Examples:
123.456 Precision=6 Scale=3
10 Precision=2 Scale=0
-96.9923 Precision=6 Scale=4
0.0 Precision=1 Scale=1
Negative Scale
For a negative scale value, we apply the following formula:
result = (given number) * 10 ^ (-(scale value))
Example
Given number = 1234.56
scale = -5
-> (1234.56) * 10^(-(-5))
-> (1234.56) * 10^(+5)
-> 123456000
Reference: https://www.logicbig.com/quick-info/programming/precision-and-scale.html

From your example annotation the maximum digits is 2 after the decimal point and 9 before (totally 11):
123456789,01

Related

DecimalFormat rounding gives different result than BigDecimal rounding

The following code gives -1 for BigDecimal and -0 for DecimalFormat. Why is there a difference? I would expect both to give -1
BigDecimal bd = new BigDecimal(-0.0009).setScale(0, RoundingMode.FLOOR);
System.out.println(bd.toPlainString());
DecimalFormat df = new DecimalFormat("0");
df.setRoundingMode(RoundingMode.FLOOR);
System.out.println(df.format(-0.0009));
Decimal format seems to return correct/expected result when 0.000 is used but gives wrong/unexpected result for 0, 0.0, 0.00 patterns
This is because .setScale & setRoundingMode working in different ways
SetScale function Usage:-
* Returns a {#code BigDecimal} whose scale is the specified
* value, and whose unscaled value is determined by multiplying or
* dividing this {#code BigDecimal}'s unscaled value by the
* appropriate power of ten to maintain its overall value. If the
* scale is reduced by the operation, the unscaled value must be
* divided (rather than multiplied), and the value may be changed;
* in this case, the specified rounding mode is applied to the division.
setScale(0) rounds 0.00009 to 1
setScale(1) rounds 0.00009 to 0.1
setScale(2) rounds 0.00009 to 0.01
setScale(3) rounds 0.00009 to 0.01
With DecimalFormat Number is rounded & digits are shown as per the format size is mentioned.
DecimalFormat("0.00") makes 0.009 to 0.01
DecimalFormat("0.0") makes 0.009 to 0.0 (rounding was 0.001)
DecimalFormat("0") makes 0.009 to 0 (rounding was 0.001)
DecimalFormat("0.000") makes 0.009 to 0.009 (rounding was not needed)

Anomalies in converting float to int in Java

As the title states, I'm trying to convert some floats to ints and there are a few anomalies i saw for a couple of values. I have a method that's trying to convert the decimal portion of a float, for example .45 from 123.45, to a string representation where it outputs as 45/100.
The problem is that for 0.35, 0.45, 0.65 and 0.95 i get 0.34, 0.44, 0.64 and 0.94 respectively. Here's my implementation:
public String convertDecimalPartToString(float input){
int numberOfDigits = numberOfDigits(input);
System.out.println(numberOfDigits);
String numerator = Integer.toString((int) (input * Math.pow(10, numberOfDigits)));
String denominator = Integer.toString((int) Math.pow(10, numberOfDigits));
return numerator + "/" + denominator;
}
public int numberOfDigits(float input){
String inputAsString = Float.toString(input);
System.out.println(inputAsString);
int digits = 0;
// go to less than 2 because first 2 will be 0 and .
for(int i =0;i<inputAsString.length()-2;i++){
digits++;
}
return digits;
}
My test method looks like this:
#Test
public void testConvertDecimalPartToString(){
float input = 0.95f;
//output is 94/100 and assert fails
Assert.assertEquals("95/100", checkWriter.convertDecimalPartToString(input));
}
It seems like there's a miscalculation in this line:
String numerator = Integer.toString((int) (input * Math.pow(10, numberOfDigits)));
but I don't understand why it works properly for 0.05, 0.15, 0.25, 0.55, 0.75 and 0.85.
Can anybody help me understand?
The problem is blessed numbers. Imagine I gave you 3 bits (0 or 1 values): you could only represent 8 different values with this: 000, 001, 010, 011, 100, 101, 110, and 111. That's all of em. Can't represent more than 8 concepts if that's the only legal values!
float is a 32-bit value. With 32 bits, I can give you up to 4 billion different values. That's a lot of values, but it is not infinite, and yet there are infinite numbers between 0 and 1, let alone between 0 and 340,282,346,638,528,860,000,000,000,000,000,000,000.000000 which is the largest value a float can represent.
So how does that work? Well, not every number is actually representable. Only about 4 billion numbers are. These are the blessed numbers.
Anytime you try to make a non-blessed number, or the result of a calculation isn't blessed, then your number is rounded to the nearest blessed number, and if you perform a sequence of operations, that rounding occurs at every step.
The nature of blessed numbers is such that there are as many blessed numbers beteen 0 and 1 as there are above 1 - as you move away from 0 the interval between any 2 blessed numbers goes up. Eventually it'll be more than 1.0, in fact.
Furthermore, computers count in binary, not decimal. Just like you cannot represent '1 divided amongst 3 things' in decimal (0.33333... it never ends), something as simple as 0.1 (1 divided amongst 10 things) cannot be perfectly represented in binary, so something as simple as 1.0/10.0 already rounds!
Your code will fail if _any_rounding occurs. The solution in your case is fairly easy; add 0.005 would do it. A better way is to first render it to a rounded string:
String x = String.format("%.02f", yourValue);
and then find what you need in the string. The above takes care of proper rounding and will do a better job than using Math.pow, which, as it moves you away from that 0, causes MORE errors to show up (further from 0 -> more extreme rounding errors, as there are fewer blessed numbers out that far).
NB: Note that double is as fast as float, and given that you have 64 bits to spend there, has way more blessed numbers, so, fewer errors.
NB2: Another way to do this is to just move your concept of a 'unit'. For example, if this represents cash, just have int cents; - no problems there, it's much easier to know what the blessed numbers are for int (every integer between -2^31 and +2^31).

Why double has specific range of values? (Java)

Why double in Java has a specific range of values from ±5,0*10(^-324) to ±1,7*10(^308)? I mean why it's not like ±5,0*10(^-324) to ±5,0*10(^308) or ±1,7*10(^-324) to ±1,7*10(^308)?
Answer to your question is subnormal numbers, check following link
https://en.wikipedia.org/wiki/Denormal_number
Double floating point numbers in Java are based on the format defined in IEEE 754.
See this link for the explanation.
https://en.wikipedia.org/wiki/Double-precision_floating-point_format
Following is a simple set of rules
Floating point number is represented in 64 bits
64 bits are divided in following
Sign bit: 1 bit (sign of the number)
Exponent: 11 bits (signed)
Significand precision (Fraction): 52 bits
Number range that we get from this setup is
-1022 <= Exponent <= 1023 (total 2046) (excluding 0 and 2047, they have special meanings)
000 (0 in base 16) is used to represent a signed zero (if F=0) and subnormals (if F≠0); and
7ff (2047 in base 16) is used to represent ∞ (if F=0) and NaNs (if F≠0),
https://en.wikipedia.org/wiki/Exponent_bias
and
-2^52 <= Fraction <= 2^52
So the minimum and maximum numbers that can be represented are
Min positive double = +1 * 2^(-1022) ≈ 2.225 * 10(−308)
Note: 1022 * Math.log(2) / Math.log(10) = 307.652
and Math.pow(10, 1 - .652) = 2.228 (.652 is approximation)
Max positive double = +(2^52) * (2^1023) = 1.797 * 10^308
So the range becomes [-2.225 * 10(−308), 1.797 * 10^308]
This range changes due to subnormal numbers
Subnormal number is a number that is smaller than the minimum normal
number defined by the specification.
If I have a number 0.00123 it would be represented as 1.23 * 10^(-3). Floating point numbers by specification don't have leading zeroes. So If there's a number with leading zeros, it adds to the default Exponent. So If I have a number with minimum exponent possible with leading zeroes, leading zeros will add to the negative exponent.
There are 52 bits for the signifand (fraction) so maximum number of leading zeros in binary can be 51. which effectively produce following number.
Min positive Subnormal = 1 * 2^-52 * (2^-1022) = 2^(-2074) ≈ 4.9·10^(−324)
Note: 1074 * Math.log(2) / Math.log(10) = 323.306
Math.pow(10, 1 - 0.306) = 4.943
So there you have it, range is now
[- Min subnormal number, + Max normal number]
or
[- 4.9 * 10^(−324), + 1.79769 *10^308]

Java's Bigdecimal.divide and rounding

At work, we found a problem when trying to divide a large number by 1000. This number came from the database.
Say I have this method:
private static BigDecimal divideBy1000(BigDecimal dividendo) {
if (dividendo == null) return null;
return dividendo.divide(BigDecimal.valueOf(1000), RoundingMode.HALF_UP);
}
When I make the following call
divideBy1000(new BigDecimal("176100000"))
I receive the expected value of 176100. But if I try the line below
divideBy1000(new BigDecimal("1761e+5"))
I receive the value 200000. Why this occurs? Both numbers are the same with different representation and the latest is what I receive from database. I understand that, somehow, the JVM is dividing the number 1761 by 1000, rounding up and filling with 0's at the end.
What is the best way to avoid this kind of behavior? Keep in mind that the original number is not controlled by me.
As specified in javadoc, a BigDecimal is defined by an integer value and a scale.
The value of the number represented by the BigDecimal is therefore
(unscaledValue × 10^(-scale)).
So BigDecimal("1761e+5") has scale -5 and BigDecimal(176100000) has scale 0.
The division of the two BigDecimal is done using the -5 and 0 scales respectively because the scales are not specified when dividing. The divide documentation explains why the results are different.
divide
public BigDecimal divide(BigDecimal divisor)
Returns a BigDecimal whose value is (this / divisor), and whose preferred scale is (this.scale() - divisor.scale()); if the exact quotient cannot be represented (because it has a non-terminating decimal expansion) an ArithmeticException is thrown.
Parameters:
divisor - value by which this BigDecimal is to be divided.
Returns:
this / divisor
Throws:
ArithmeticException — if the exact quotient does not have a terminating decimal expansion
Since:
1.5
If you specify a scale when dividing, e.g. dividendo.divide(BigDecimal.valueOf(1000), 0, RoundingMode.HALF_UP) you will get the same result.
The expressions new BigDecimal("176100000") and new BigDecimal("1761e+5") are not equal. BigDecimal keeps track of both value, and precision.
BigDecimal("176100000") has 9 digits of precision and is represented internally as the BigInteger("176100000"), multiplied by 1. BigDecimal("1761e+5") has 4 digits of precision and is represented internally as the BigInteger("1761"), multiplied by 100000.
When you a divide a BigDecimal by a value, the result respects the digits of precision, resulting in different outputs for seemingly equal values.
for your division with BigDecimal.
dividendo.divide(divisor,2,RoundingMode.CEILING)//00.00 nothing for up and nothing for down
in this operation have a precision for two decimals.
To avoid this kind of problems in Java when dividing by powers of 10 you have a much efficient and precise approach:
dividendo.movePointLeft(3)
Yeah, that's kind of issue what you're experimenting. If I may, in a situation where you only have exponental numbers, you should cast them and then use your method. See what I suggest is this bit of code down there:
long longValue = Double.valueOf("1761e+5").longValue();
BigDecimal value= new BigDecimal(longValue);
Use it in a method which would convert those string into a new BigDecimal and return this BigDecimal value. Then you can use those returned values with divideBy1000.That should clear any issue you're having.
If you have a lot of those, what you can do also in store those BigDecimal in a data structure like a list. Then use a foreach loop in which you apply divideBy1000 and each new value would be stored in a different list. Then you would just have to access this list to have your new set of values !
Hope it helps :)
Try using round().
private static BigDecimal divideBy1000(BigDecimal dividendo) {
if (dividendo == null) return null;
return dividendo.divide(BigDecimal.valueOf(1000)).round(new MathContext(4, RoundingMode.HALF_UP));
}
public static void main(String []args){
BigDecimal bigD = new BigDecimal("1761e5");
BigDecimal bigDr = divideBy1000(bigD);
System.out.println(bigDr);
}
The new MathContext(4, RoundingMode.HALF_UP)) line returns the division to 4 places.
This produces:
1.761E+5
Which is what you want. (:
Any time you are multiplying a BigDecimal by a power of 10, in this case you are multiplying by 10-3, you can use dividendo.scaleByPowerOfTen(power) which only modifies the scale of the BigDecimal object and side steps any rounding issues, or at least moves them to a later calculation.
The other answers here cover the more general case of dividing by any number.
I want to quote basic concepts for BigDecimal:
A BigDecimal consists of an arbitrary precision integer unscaled value and a 32-bit integer scale.
public class BigDecimal extends Number implements Comparable<BigDecimal> {
// …
private final BigInteger intVal;
private final int scale;
}
That is, BigDecimal number is represented as unscaled integer value * 10^(-scale)
For example, 1.234 = 1234 * 10^(-3). So, precision is 4, scale is 3.
Please refer to basic concept in here.
For the former:
BigDecimal bd1 = new BigDecimal("176100000");
System.out.println(bd1.precision()); // 9
System.out.println(bd1.scale()); // 0
BigDecimal bd2 = BigDecimal.valueOf(1000);
System.out.println(bd2.precision()); // 4
System.out.println(bd2.scale()); // 0
System.out.println(bd1.divide(bd2)); // 176100
System.out.println(bd1.divide(bd2, RoundingMode.HALF_UP)); // 176100
BigDecimal result = bd1.divide(bd2);
System.out.println(result.precision()); // 6
System.out.println(result.scale()); // 0
The new BigDecimal("176100000")'s precision is 9 and scale is 0.
The BigDecimal.valueOf(1000)'s precision is 4 and scale is 0.
(176100000 * 10^0) / (1000 * 10^0) = 176100 * 10^0.
With method public BigDecimal divide​(BigDecimal divisor, RoundingMode roundingMode), we have to use the dividend(new BigDecimal("176100000"))'s scale as a scale of returning BigDecimal. In this case, the scale is 0.
Returns a BigDecimal whose value is (this / divisor), and whose scale is this.scale().
As a result, we have BigDecimal number 176100 * 10^0 whose precision is 6 and scale is 0.
The rounding is applied, but the result is integer already, so we just get 176100.
For the latter:
BigDecimal bd1 = new BigDecimal("1761e+5");
System.out.println(bd1.precision()); // 4
System.out.println(bd1.scale()); // -5
BigDecimal bd2 = BigDecimal.valueOf(1000);
System.out.println(bd2.precision()); // 4
System.out.println(bd2.scale()); // 0
System.out.println(bd1.divide(bd2)); // 1.761E+5
System.out.println(bd1.divide(bd2, RoundingMode.HALF_UP)); // 2E+5
BigDecimal result1 = bd1.divide(bd2);
System.out.println(result1.precision()); // 4
System.out.println(result1.scale()); // -2
BigDecimal result2 = bd1.divide(bd2, RoundingMode.HALF_UP);
System.out.println(result2.precision()); // 1
System.out.println(result2.scale()); // -5
The new BigDecimal("1761e+5")'s precision is 4 and scale is -5.
The BigDecimal.valueOf(1000)'s precision is 4 and scale is 0.
(1761 * 10^(-(-5))) / (1000 * 10^0) = 1.761 * 10^(-(-5))
= 1761 * 10^(-(-2)) whose precision is 4 and scale is -2; prints "1.761E+5" using scientific notation of overriden toString.
If we apply rounding, 1.761 * 10^(-(-5)) = 2 * 10^(-(-5)) whose precision is 1 and scale is -5; prints "2E+5" using scientific notation of overriden toString.
I am might be wrong. If you could catch my mistakes, please comment to this answer. I'll correct them.

How to round the floating point precision, regard to the size of it?

It's easy to round the value to a specific number of decimal positions:
public static double round(double x, int n){
n = (int)Math.pow(n, 10);
return Math.round(x*n)/n;
}
But can you make an algorithm that can presuppose how many decimal places it would actually need to round up depending on how large the number is?
I mean, if the number is huge (like 100000000.12345) it wouldn't need that much of decimal precision so it can round it up to lets say 1 or 2 decimal places,
While if a number is pretty slight (say like 0.00012345) it would need maximum decimal precision
What's the idea to do something like this?
EDIT:
Example:
argument returned decnum comment
123456789.9 123456789 0 //number is way too large so I don't need any decimal places
12345678.99 12345678 0 //number is still very large.
1234567.899 1234567 0 //still...
123456.7899 123456 0 //...
12345.67899 12345 0 //...
1234.567899 1234 0 //It's pretty large now. Still not small enough to get some decimals.
123.4567899 123.4 1 //Now, number is a little average, so 1 decimal place is included.
12.34567899 12.34 2 //The number is normal, so I want a normal 2 decimal places
1.234567899 1.234 3 //Now it's kinda small, so it now gives 3 decimal places
.1234567899 0.1234 4 //Smaller number, thus greater number of decimal places (4).
.0123456789 0.01234 5 //Even smaller
.0012345678 0.0012345 7 //Larger number of decimal places.
.0001234567 0.0001234567 10 //Smaller and smaller number, greater and greater number of decimals.
I'm just looking for a waywhere I can INCREASE number of decimals when the number is getting closer to zero,
And DECREASE the decimal-number as the number gets away from zero.
You could use BigDecimal. Create a BigDecimal with your double value then check the scale with bigDVariable.scale() and round with the wanted scale depending on the value returned by it and its value. BigDecimal doc.
Edit : You don't seem to worry about how many decimals you have in the first place, the value of your double looks to be the only information that matters. Checking scale() wouldn't matter then.
From algorithm point of view, I would follow the steps below:
Convert the number in base 10 format e.g. 123456789.9 -> 1.234567899 * 10^8 and 0.0001234567 --> 1.234567 * 10^-4.
Look at the power factors of 10 e.g. 8 in the first example and -4 in the last example.
Compute the precision place requirement by adjusting a factor. Just an example could be as (-1*(p-3)), where p is the power factor. If the number comes negative then use '0'.
This will result into following numbers.
argument decnum
123456789.9 -1*(8-3) = -5 -> 0
12345678.99 -1*(7-3) = -4 -> 0
1234567.899 -1*(6-3) = -3 -> 0
123456.7899 -1*(5-3) = -2 -> 0
12345.67899 -1*(4-3) = -1 -> 0
1234.567899 -1*(3-3) = 0 -> 0
123.4567899 -1*(2-3) = 1 -> 1
12.34567899 -1*(1-3) = 2 -> 2
1.234567899 -1*(0-3) = 3 -> 3
.1234567899 -1*(-1-3) = 4 -> 4
.0123456789 -1*(-2-3) = 5 -> 5
.0012345678 -1*(-3-3) = 6 -> 6
.0001234567 -1*(-4-3) = 7 -> 7
This would be very close to what you are looking for. Try adjusting the factor and the formula to get more closer, if you really want to (this is just an algorithm/aproach).
If you want high precision (significant digits), you'll have to use BigDecimal instead of double. The BigDecimal round method gives you the number of significant digits you ask for.
Here's something I put together.
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
public class Rounding {
public static void main(String[] args) {
BigDecimal number = new BigDecimal(100000000.12345);
System.out.println("Number: " + number.toPlainString());
System.out.println(" ");
for (int precision = 8; precision < 17; precision++) {
System.out.println("Precision: " + precision + ", result: "
+ round(number, precision));
}
}
public static String round(BigDecimal number, int precision) {
MathContext mathContext = new MathContext(precision,
RoundingMode.HALF_UP);
BigDecimal rounded = number.round(mathContext);
return rounded.toPlainString();
}
}
And here are the results.
Number: 100000000.12344999611377716064453125
Precision: 8, result: 100000000
Precision: 9, result: 100000000
Precision: 10, result: 100000000.1
Precision: 11, result: 100000000.12
Precision: 12, result: 100000000.123
Precision: 13, result: 100000000.1234
Precision: 14, result: 100000000.12345
Precision: 15, result: 100000000.123450
Precision: 16, result: 100000000.1234500
.
Number: 0.00012344999999999999203137424075293893110938370227813720703125
Precision: 8, result: 0.00012345000
Precision: 9, result: 0.000123450000
Precision: 10, result: 0.0001234500000
Precision: 11, result: 0.00012345000000
Precision: 12, result: 0.000123450000000
Precision: 13, result: 0.0001234500000000
Precision: 14, result: 0.00012345000000000
Precision: 15, result: 0.000123450000000000
Precision: 16, result: 0.0001234500000000000

Categories