0

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.

bsobaid
  • 955
  • 1
  • 16
  • 36

3 Answers3

1

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

JustAnotherDeveloper
  • 2,061
  • 2
  • 10
  • 24
  • interesting, so we set the rouding mode as down but by virtue of decimal format combo it still wont round the number ? – bsobaid Oct 29 '20 at 12:28
  • 1
    @bsobaid If the javadoc didn't lie to me, it doesn't round the number because that rounding mode rounds towards zero, i.e it never increments the digit before the discarded decimals. For example, `RoundingMode.DOWN` rounds 1.7 to 1. Since we're displaying up to 4 decimal places this only comes into play with the first test value, which rounded to four decimal places in that rounding mode renders 4.1289. However, please test it with other values, I might have missed a corner case that won't work like the others. – JustAnotherDeveloper Oct 29 '20 at 13:03
1

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;
    }
    
}
Spectric
  • 30,714
  • 6
  • 20
  • 43
1

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

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72