I do not want to limit the number of significant digits in a BigDecimal. I only want to find the number of significant digits that number has.
Is there a way to do this without converting the number to string and count the number characters?
I believe you want a combination of stripTrailingZeros, precision and scale, as demonstrated here:
import java.math.*;
public class Test {
public static void main(String[] args) {
test("5000"); // 4
test("5000.00"); // 4
test("5000.12"); // 6
test("35000"); // 5
test("35000.00"); // 5
test("35000.12"); // 7
test("35000.120"); // 7
test("0.0034"); // 2
test("1.0034"); // 5
test("1.00340"); // 5
}
private static void test(String input) {
System.out.println(input + " => " +
significantDigits(new BigDecimal(input)));
}
private static int significantDigits(BigDecimal input) {
input = input.stripTrailingZeros();
return input.scale() < 0
? input.precision() - input.scale()
: input.precision();
}
}
The call to stripTrailingZeros is required as otherwise it's entirely possible for a BigDecimal to be stored in a "non-normalized" form. For example, new BigDecimal(5000) has a precision of 4, not 1.
The call to scale() is used to handle cases where the normalized form has trailing zeroes before the decimal point, but nothing after the decimal point. In this case, the scale will always be negative, and indicates the number of trailing zeroes.
EDIT: Cases with trailing zeroes but no decimal point are inherently ambiguous - there's no definite number of significant digits to "5000" for example. The above code treats all trailing zeroes before the decimal point as significant.
The following modification of Jon's answer returns the results that seem correct to me:
private static int significantDigits(BigDecimal input) {
return input.scale() <= 0
? input.precision() + input.stripTrailingZeros().scale()
: input.precision();
}
(Note that input.stripTrailingZeros().scale() appears to always be negative in these tests.)
Also, as I noted above, BigDecimal isn't capable of distinguishing between, say, a "5000" with one significant digit and a "5000" with two, for example. Furthermore, according to the definitions, "5000." (with a trailing decimal point) should have exactly four significant digits, but BigDecimal isn't capable of handling that. (See http://en.wikipedia.org/wiki/Significant_figures for the definitions I'm using.)
Jon's answer is correct in most cases except exponential number:
private static int significantDigits(BigDecimal input) {
return input.scale() <= 0
? input.precision() + input.stripTrailingZeros().scale()
: input.precision();
}
let's say the input as 1.230000E17, the function returns 18 however the correct significant digits should be 7.
Related
I want to round off any double to a String with 2 decimal places in Java.
I have tried using DecimalFormat but it doesn't give the expected results.
Any help would be appreciated.
Ex: I/P: 3402100.5323
I want to convert this to:
O/P: 34.02
I've tried using DecimalFormat("##,##,##0.00", new DecimalFormatSymbols(Locale.US))
but this results in 34,02,100.53 whereas I want it to output 34.02
PS: If the I/P is 34.02 I would expect it to remain same even after applying the formatting
In my opinion, this can be achieved in 2 steps:
Transform the number into your customised
round-off. (3402100.5323 to 34.021005323). Divide the input with power of 10 to make it round to 2 digits.
Then transformed number can be pretty-printed to truncate value after 2 decimals (34.021005323 to 34.02)
public static void main(String[] args) {
double input = 3402100.5323;
double output = input / getDivisor(input);
System.out.printf("%.2f%n", output);
}
private static double getDivisor(double input) {
int length = String.valueOf((long) input).length();
return Math.pow(10, length - 2) ;
}
Output: 34.02
String.format("%0.2f", 34.021005323)
See
https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#format(java.lang.String,%20java.lang.Object...) and
https://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html#syntax
Turning one number into something completely different is, naturally, not the job of decimalformat.
To get from a number representing 3402100.5323 to the string "34.02", first you'd have to get a number that is closer to "34.02". In other words, divide by 10000.0 first.
From there, String.format("%.2f") seems like an easy path: That renders any double to a string, but never using more than 2 digits after the decimal separator. If you want 3400000.123 to turn into "34.00" and not "34", you can make that String.format("%.02f") to force the zeroes.
public String renderWhateverThatIs(double in) {
return String.format("%.02f", in / 100000.0);
}
renderWhateverThatIs(3402100.5323);
> 34,02
Note that the machine locale will dictate if you see a dot or a comma as separator. You can force the issue by explicitly passing a locale to format.
I think what you're looking for is the java.math.BigDecimal class (https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html).
In your case, it would look like this:
BigDecimal rounded = BigDecimal.valueOf(yourDoubleValueHere).setScale(2, BigDecimal.ROUND_HALF_UP);
System.out.println(rounded); // 34.02
It can replace doubles (with more complex syntax though) by basically storing numbers in their decimal form, which means you could make operations on it and keep having two decimal places and avoid rounding issues.
EDIT: after thinking about it, it's probably overkill since you only want to get a String with the rounded value, but I'll leave it there just in case.
I don’t believe you can achieve what you want (First 4 digits converted into a 2 digit double with 2 decimal places) in a single step. I’ll break down the steps for an approach that I would try:
Convert the input double to a string
double d = 3402100.5323;
String dStr1 = String.valueOf(d); // dStr1 will be “3402100.5323”
Next, remove the decimal from the string
String dStr2 = dStr1.replace(‘.’,’’); // dStr2 will be “34021005323”
Then, grab the first 4 digits you are interested in
String dStr3 = dStr2.substring(0,4); // dStr3 will be “3402”
Finally, insert a decimal point
String result = dStr3.substring(0,2) + “.” + dStr3.substring(2); // result will be “34.02”
You can use format for this try this out it work 100% for me.
String.format("%.2f", value)
Helpful link
https://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html#syntax
I want to simply have a function that converts a double with as many decimal places into 4 decimal places without rounding.
I have this code that has been working fine but found a random instance where it turned .0 into .99
Here are some sample outputs
4.12897456 ->4.1289
4.5 ->4.5
4.5231->4.5231
5.53->5.53
5.52->5.199 (Wrong conversion, I want it to be 5.52)
private static double get4Donly(double val){
double converted = ((long)(val * 1e4)) / 1e4;
return converted
}
EDIT: This conversion is called thousands of times, so please suggest a method where I dont have to create a new string all the time.
You can use DecimalFormat
import java.text.DecimalFormat;
import java.math.RoundingMode;
import java.util.Arrays;
public class MyClass {
public static void main(String args[]) {
DecimalFormat df = new DecimalFormat("#.####");
df.setRoundingMode(RoundingMode.DOWN);
for (Number n : Arrays.asList(4.12897456, 4.5, 4.5231, 5.53, 5.52)) {
Double d = n.doubleValue();
System.out.println(df.format(d));
}
}
}
RoundingMode.DOWN rounds towards zero, new DecimalFormat("#.####") creates a DecimalFormat instance that formats numbers to a maximum of 4 decimal places. Put those two together and the above code produces the following output, which I believe matches your expectations:
4.1289
4.5
4.5231
5.53
5.52
Doubles just don't work like you think they do.
They are stored in a binary form, not a decimal form. Just like '1 divided by 3' is not representable in a decimal double (0.3333333333 is not enough, it's infinite 3s, so not representable, so you get a rounding error), but '1 divided by 5' is representable just fine, there are numbers that are representable, and numbers that end up rounded when storing things in a double type, but crucially things that seem perfectly roundable in decimal may not be roundable in binary.
Given that they don't match up, your idea of 'eh, I will multiply by 4, turn it to a long, then convert back to a double, then divide by 1000' is not going to let those digits go through unmolested. This is not how you round things, as you're introducing additional loss in addition to the loss you already started out with due to using doubles.
You have 3 solutions available:
Just print it properly
A double cannot be considered to 'have 4 digits after the decimal separator' because a double isn't decimal.
Therefore, it doesn't even make sense to say: Please round this double to at most 4 fractional digits.
That is the crucial realisation. Once you understand that you'll be well along the way :)
What you CAN do is 'please take this double and print it by using no more than 4 digits after the decimal separator'.
String out = String.format("%.4f", 5.52);
or you can use System.printf(XXX) which is short for System.print(String.format(XXX)).
This is probably what you want
forget doubles entirely
For some domains its better to ditch doubles and switch to longs or ints. For example, if you're doing finances, it's better to store the atomic unit as per that currency in a long, and forego doubles instead. So, for dollars, store cents-in-a-long. For euros, the same. For bitcoin, store satoshis. Write custom rendering to render back in a form that is palatable for that currency:
long value = 450; // $4.50
String formatCurrency(long cents) {
return String.format("%s%s%d.%02d", cents < 0 ? "-" : " ", "$", Math.abs(cents) / 100, Math.abs(cents) % 100);
}
Use BigDecimal
This is generally more trouble than it is worth, but it stores every digit, decimally - it represent everything decimal notation can (and it also cannot represent anything else - 1 divided by 3 is impossible in BigDecimal).
I would recommend using the .substring() method by converting the double to a String. It is much easier to understand and achieve since you do not require the number to be rounded.
Moreover, it is the most simple out of all the other methods, such as using DecimalFormat
In that case, you could do it like so:
private static double get4Donly(double val){
String num = String.valueOf(val);
return Double.parseDouble(num.substring(0, 6));
}
However, if the length of the result is smaller than 6 characters, you can do:
private static double get4Donly(double val){
String num = String.valueOf(val);
if(num.length()>6) {
return Double.parseDouble(num.substring(0, 6));
}else {
return val;
}
}
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
I have an assignment in which I need to create a function that tells you how many 1's are in the binary notation of a integer. I did this already by creating my own algorithm. The second step is to use java.math.BigInteger.bitCount() to accomplish the same thing. I looked this up in the Java API but can someone put this into English and explain how it's relevant to finding the number of 1's in the binary notation of an integer, and perhaps also an example. I tried googling but found nothing but the following definition.
public int bitCount()
Description:
Returns the number of bits in the two's complement representation of this number that differ from its sign bit. This method is useful when implementing bit-vector style sets atop BigIntegers.
In the two's complement representation of a negative integer, the sign bit is 1, whereas in that of a non-negative integer, the sign bit is 0. So for a positive integer, bitCount() returns the number of bits that are not 0, i.e., that are 1.
Read the javadoc again, and then assume a positive number -- which means that the sign bit will be zero.
Once you understand that, think about what to do in the negative case.
jcomeau#intrepid:/tmp$ cat test.java; javac test.java; java test
import java.math.BigInteger;
public class test {
public static void main(String[] args) {
System.out.println("one bits: " + new BigInteger("0f0f0f0f0f0f0", 16).bitCount());
}
}
one bits: 24
Modified code for new comment:
jcomeau#intrepid:/tmp$ cat test.java; javac test.java; java test 0xf0f0f0f0f0f0 0x200 200 1 0
import java.math.BigInteger;
public class test {
public static void main(String[] args) {
BigInteger number = null;
for (String arg : args) {
if (arg.startsWith("0x")) {
number = new BigInteger(arg.substring(2), 16);
} else {
number = new BigInteger(arg); // decimal by default
}
System.out.println("one bits in " + arg + ": " + number.bitCount());
}
}
}
one bits in 0xf0f0f0f0f0f0: 24
one bits in 0x200: 1
one bits in 200: 3
one bits in 1: 1
one bits in 0: 0
Obviously, from reading the javadoc you posted, this may or may not conform to what you or your professor expect for negative numbers. This will return 0 "1" bits for negative 1, for example, since all the bits are the same as the sign bit. But it should work for all positive values.
The most significant bit of a signed integer is the sign-bit. If the number is >= 0 then the sign bit is 0. If the number is < 0 then the sign-bit is 1.
So the function gives you the number of bits that different from the sign-bit. Makes the job pretty easy for positive numbers. The answer for negative numbers will also depend on the total numbers of bits being used to represent the number.
Why don't you just use Integer's bitCount (not BigInteger's)? It returns the number of 1 bits, regardless of sign.
What would be a good way to trim more than two trailing zeros for a BigDecimal
So 1.2200 would print 1.22 and 1.0000 would print 1.00
Edit As well as to return 1.222200 as
1.2222 and 1.220000001 as 1.220000001 etc. So disregarding first two zeros I want to trim any incoming 0s and not trim non-zero values
One way could be to multiply, then apply the built in trim trailing zeros and then divide by 100. It could be problematic with corner cases but the values in my problem are currency based and would never exceed the bounds set by Java (or else it means my software is dealing with bids which are in gazzilions of dollars)
The ugly solution is as folows
System.out.println(((new BigDecimal("1.230223000")
.multiply(new BigDecimal("100.0"))
.stripTrailingZeros()).divide(new BigDecimal("100.0"))));
Update: Having those mixed requirements (i.e. at least 2 digits after the decimal point should be displayed, but as many as necessary) is not trivially implemented, but you can come close:
Combine stripTrailingZeros() with DecimalFormat to get the desired behaviour (or close to it):
DecimalFormat df = new DecimalFormat("0.00########")
String formatted = df.format(bigDecimal.stripTrailingZeros())
This will format any BigDecimal value with at least 2 digits after the decimal point and up to 10 digits after the decimal point, if it improves the precision.
BigDecimal values with more than 10 digits after the decimal point will still be cut off:
input | output
-----------------+----------
1.20000 | 1.20
1.23000 | 1.23
1.2301 | 1.2301
1.230001000 | 1.230001
1.2300000000001 | 1.23
Original answer:
If you always want to have exactly 2 digits after the comma and know that you won't lose precision this way, then you can call setScale(2, RoundingMode.UNNECESSARY):
System.out.println(new BigDecimal("1.23000").setScale(2, RoundingMode.UNNECESSARY));
This code will print 1.23. Note that this will throw an ArithmeticException when rounding would be necessary (i.e. anything after the first 2 digits is not zero).
If your values can have a higher precision and you want to apply some rounding, simply replace RoundingMode.UNNECESSARY with the appropriate value:
System.out.println(new BigDecimal("1.2301").setScale(2, RoundingMode.CEILING));
This will print 1.24.
If you don't know the exact number of digits but want as few as possible (i.e. you want the smallest possible scale for your BigDecimal) then calling stripTrailingZeros() will do exactly what you want:
System.out.println(new BigDecimal("1.230001000").stripTrailingZeros();
This will print 1.230001.
Check this,
import java.text.DecimalFormat;
import java.text.NumberFormat;
public class DecimalFormatExample
{
public static void main(String args[])
{
double amount = 2192.015;
NumberFormat formatter = new DecimalFormat("#0.00");
System.out.println("The Decimal Value is:"+formatter.format(amount));
}
}
This method will give you the result you want (monetary round):
(what is String because it's better for BigDecimal see documentation)
public static float roundUp(String what, int howmuch) throws Exception{
try {
return (new BigDecimal(what).setScale(howmuch, BigDecimal.ROUND_UP)).floatValue();
} catch (NumberFormatException nfe) {
throw new Exception("BigDecimal cannot parse value : " + what, nfe);
}
}
If it's for displaying purposes use:
BigDecimal d = new BigDecimal("1.2200");
NumberFormat n = NumberFormat.getCurrencyInstance(Locale.US);
String s = n.format(d.doubleValue());
For outputting as String, use DecimalFormat.
Otherwise, use this:
public static BigDecimal stripToMinimumScale(BigDecimal value,
final int minimumScale) {
if (value.scale() == minimumScale) // Already correct scale
return value;
else {
value = value.stripTrailingZeros();
return (value.scale() < minimumScale) ?
value.setScale(minimumScale) : // Too few decimals, needs zero pad
value; // Do not round any significant digits
}
}
BigDecimal d = new BigDecimal("59.0000");
String d1 = new DecimalFormat().format(d);
System.out.println("d1 is " + d1);