Reputation: 38643
I'm working on a Java/Groovy program. I have a double variable that holds a number that was typed in by a user. What I really want to know is how many numbers the user typed to the right of the decimal place. Something like:
double num = 3.14
num.getPlaces() == 2
Of course, you can't do this with a double since that's using IEEE floating points and it's all an approximation.
Assuming that I can't get at the string the user typed, but only have access to the double the value has been stored in, is there a way I can scrub that double though a BigDecimal or somesuch to get the "real" number of decimal places? (When the double gets displayed on the screen, it gets it right, so I assume there is a way to at least guess well?)
Upvotes: 3
Views: 6818
Reputation: 4274
Yes, you can. At least if you don't mind that you sometimes get a wrong result. We must assume that the user is lazy and has entered no more than ~12 significant decimal digits.
Then do the following:
Create a BigDecimal with the given double in the constructor. That will convert the double in an exact decimal representation.
Get only the fraction.
Get BigDecimal.toString() and find three or more consecutive '0' or '9' digits.
Cut off the fraction after those three digits. If the digits are nine, add a "1" at the end. After that remove all trailing zeroes.
Count the remaining fractional digits
Warning: This may be a bit slow.
If you only want the number of significant bits in the double you can get them much faster and easier, but decimal->binary conversion unfortunately uses almost all bits.
Upvotes: 2
Reputation: 96424
You are right there is something strange going on with doubles, what gets printed out is not the same as the contents of the variable.
for example:
groovy:000> "123.0001".toBigDecimal()
===> 123.0001
groovy:000> "123.0001".toDouble()
===> 123.0001
groovy:000> new BigDecimal("123.0001".toDouble())
===> 123.000100000000003319655661471188068389892578125
Notice the damage is done in the conversion of the string to a double, not when the double is passed into the BigDecimal. Feeding the double to the BigDecimal just provides an easy way to see what's actually in the double, because toString is lying to you.
As Jon Skeet points out, accuracy is not an option here. However, assuming the value printed out on the screen is the result of calling toString on the double, you should be able to get a bigDecimal that's no more wrong than the toString version of the double, like this:
groovy:000> d = "123.0001".toDouble()
===> 123.0001
groovy:000> d.toString()
===> 123.0001
groovy:000> new BigDecimal(d.toString())
===> 123.0001
So you don't need to involve the BigDecimal, really, you can just do something like
groovy:000> d = 123.0001
===> 123.0001
groovy:000> s = d.toString()
===> 123.0001
groovy:000> s.substring(s.indexOf('.')).length() - 1
===> 4
Sorry to invalidate your comment by editing.
BTW here's something close to Steve's answer, translated to groovy. (I took out the test for the decimal point not found because if you run this on a machine with locales messed up so it isn't using a period for the decimal point I'd rather it blow up than return 0)
def getPlaces(d) {
s = d.toString()
s.substring(s.indexOf(".")).length() - 1
}
Upvotes: 3
Reputation: 1502016
No, you can't... because there are lots of different strings the user could have typed in which would all be parsed to the same value.
The "real" number is almost certain to have more decimal places than the user typed. For example, 3.14 is stored as exactly 3.140000000000000124344978758017532527446746826171875. I don't think you want to display that to the user.
To make the problem clearer, you can't tell from the double value whether the user actually entered:
3.14
3.140
3.140000
3.14000000000000012434
Those four strings will all give you the same value - which should make it obvious that you can't possibly get back to what the user typed in.
If at all possible, change the parsing code and use BigDecimal
throughout.
Upvotes: 14
Reputation: 974
If you absolutely must take a double, and you don't mind having a little utility method to run it through, you could write something like this. You said you don't have access to the String value of what the user entered, but is there a reason why you can't convert it to a string and do something like this?
static int getPlaces(double num) {
String numString = String.valueOf(num);
return numString.indexOf(".0")==numString.length()-2?0:numString.length()-numString.indexOf(".")-1;
}
then, instead of your example
num.getPlaces() == 2
You can do
getplaces(num) == 2
I hope this helps..
Update, given your comment
That the user entered. Good point.
If the user entered what looks like an integer (say, 5) without a decimal point, and you're receiving it as a double, then what you're going to get is something other than what the user entered - for s/he will have entered 5, but you'll have received 5.0. You'd have no way of telling whether the user actually entered 5 or 5.0.
Upvotes: 1
Reputation: 5043
so if it is a user entered value. Accept the value as a String. Then you can use string functions to find the position of the "." and then subtract that number from the length of the string to get the number your looking for. Of course you'll want to trim() it, and verify it is in fact a number that was entered..
Upvotes: 0
Reputation: 91482
if you're stuck with a double, convert to a string and count the number of characters after the decimal point. I think there is some magic involved that displays numbers like 1.99999999998 as "2"
Upvotes: 0