Issue
Currently i have this method:
static boolean checkDecimalPlaces(double d, int decimalPlaces){
if (d==0) return true;
double multiplier = Math.pow(10, decimalPlaces);
double check = d * multiplier;
check = Math.round(check);
check = check/multiplier;
return (d==check);
}
But this method fails for checkDecmialPlaces(649632196443.4279, 4)
probably because I do base 10 math on a base 2 number.
So how can this check be done correctly?
I thought of getting a string representation of the double value and then check that with a regexp - but that felt weird.
EDIT: Thanks for all the answers. There are cases where I really get a double and for those cases I implemented the following:
private static boolean checkDecimalPlaces(double d, int decimalPlaces) {
if (d == 0) return true;
final double epsilon = Math.pow(10.0, ((decimalPlaces + 1) * -1));
double multiplier = Math.pow(10, decimalPlaces);
double check = d * multiplier;
long checkLong = (long) Math.abs(check);
check = checkLong / multiplier;
double e = Math.abs(d - check);
return e < epsilon;
}
I changed the round
to a truncation. Seems that the computation done in round
increases the inaccuracy too much. At least in the failing testcase.
As some of you pointed out if I could get to the 'real' string input I should use BigDecimal
to check and so I have done:
BigDecimal decimal = new BigDecimal(value);
BigDecimal checkDecimal = decimal.movePointRight(decimalPlaces);
return checkDecimal.scale() == 0;
The double
value I get comes from the Apache POI API that reads excel files. I did a few tests and found out that although the API returns double
values for numeric cells I can get a accurate representation when I immediately format that double
with the DecimalFormat
:
DecimalFormat decimalFormat = new DecimalFormat();
decimalFormat.setMaximumIntegerDigits(Integer.MAX_VALUE);
// don't use grouping for numeric-type cells
decimalFormat.setGroupingUsed(false);
decimalFormat.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US));
value = decimalFormat.format(numericValue);
This also works for values that can't be represented exactly in binary format.
Solution
The test fails, because you have reached the accuracy of the binary floating point representation, which is approximately 16 digits with IEEE754 double precision. Multiplying by 649632196443.4279 by 10000 will truncate the binary representation, leading to errors when rounding and dividing afterwards, thereby invalidating the result of your function completely.
For more details see http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems
A better way would be to check whether the n+1
decimal places are below a certain threshold. If d - round(d)
is less than epsilon
(see limit), the decimal representation of d
has no significant decimal places. Similarly if (d - round(d)) * 10^n
is less than epsilon
, d can have at most n
significant places.
Use Jon Skeet's DoubleConverter
to check for the cases where d
isn't accurate enough to hold the decimal places you are looking for.
Answered By - David Schmitt Answer Checked By - Candace Johnson (PHPFixing Volunteer)
0 Comments:
Post a Comment
Note: Only a member of this blog may post a comment.