What is the best way to separate double into two parts “integer & fraction” in java

前端 未结 5 972
礼貌的吻别
礼貌的吻别 2021-02-14 00:28

I have tried to separate 5.6 (for example) by the following method:

private static double[] method(double d)
{
    int integerPart = 0;
    double fractionPart =         


        
相关标签:
5条回答
  • 2021-02-14 00:33

    To see what is going on, take a look at the binary representations of the numbers:

    double d = 5.6;
    System.err.printf("%016x%n", Double.doubleToLongBits(d));
    double[] parts = method(d);
    System.err.printf("%016x %016x%n",
                      Double.doubleToLongBits(parts[0]),
                      Double.doubleToLongBits(parts[1]));
    

    output:

    4016666666666666
    4014000000000000 3fe3333333333330
    

    5.6 is 1.4 * 22, but 0.6 is 1.2 * 2-1. Because it has a lower exponent, normalization causes the mantissa to be shifted three bits to the left. The fact that the recurring terms (..66666..) were originally an approximation of the fraction 7/5 has been forgotten, and the missing bits are replaced with zeros.

    Given the original double value as input to your method, there is no way to avoid this. To preserve the exact value you would need to use a format that represents the desired value exactly, e.g. Fraction from Apache commons-math. (For this specific example with d=5.6 a BigDecimal would also be able to represent it exactly, but there are other numbers it cannot represent exactly, e.g. 4/3)

    0 讨论(0)
  • 2021-02-14 00:33

    poor-man solution (using String)

        static double[] sp(double d) {
            String str = String.format(Locale.US, "%f", d);
            int i = str.indexOf('.');
            return new double[] {
                Double.parseDouble(str.substring(0, i)),
                Double.parseDouble(str.substring(i))
            };
        }
    

    (Locale so we really get a decimal point)

    0 讨论(0)
  • 2021-02-14 00:57

    Here is another solution based on BigDecimal (that does not go through a String).

    private static double[] method(double d) {
        BigDecimal bd = new BigDecimal(d);
        return new double[] { bd.intValue(),
                              bd.remainder(BigDecimal.ONE).doubleValue() };
    }
    

    As you'll note, you still won't get just 0.6 as output for the fractional part. (You can't even store 0.6 in a double!) This is due to the fact that the mathematical, real number, 5.6 is actually not represented by a double exactly as 5.6 but as 5.599999...


    You could also do

    private static double[] method(double d) {
        BigDecimal bd = BigDecimal.valueOf(d);
        return new double[] { bd.intValue(),
                              bd.remainder(BigDecimal.ONE).doubleValue() };
    }
    

    which actually does yield [5.0, 0.6].

    The BigDecimal.valueOf is in most JDK's (internally) implemented through a call to Double.toString however. But at least the string-related stuff doesn't clutter your code :-)


    Good follow-up question in comment:

    If it is represented as 5.599999999..., then why Double.toString(5.6) gives exactly "5.6"

    The Double.toString method is actually very sophisticated. From the documentation of Double.toString:

    [...]

    How many digits must be printed for the fractional part of m or a? There must be at least one digit to represent the fractional part, and beyond that as many, but only as many, more digits as are needed to uniquely distinguish the argument value from adjacent values of type double. That is, suppose that x is the exact mathematical value represented by the decimal representation produced by this method for a finite nonzero argument d. Then d must be the double value nearest to x; or if two double values are equally close to x, then d must be one of them and the least significant bit of the significand of d must be 0.

    [...]

    The code for getting the characters "5.6" boils down to FloatingDecimal.getChars:

    private int getChars(char[] result) {
        assert nDigits <= 19 : nDigits; // generous bound on size of nDigits
        int i = 0;
        if (isNegative) { result[0] = '-'; i = 1; }
        if (isExceptional) {
            System.arraycopy(digits, 0, result, i, nDigits);
            i += nDigits;
        } else {
            if (decExponent > 0 && decExponent < 8) {
                // print digits.digits.
                int charLength = Math.min(nDigits, decExponent);
                System.arraycopy(digits, 0, result, i, charLength);
                i += charLength;
                if (charLength < decExponent) {
                    charLength = decExponent-charLength;
                    System.arraycopy(zero, 0, result, i, charLength);
                    i += charLength;
                    result[i++] = '.';
                    result[i++] = '0';
                } else {
                    result[i++] = '.';
                    if (charLength < nDigits) {
                        int t = nDigits - charLength;
                        System.arraycopy(digits, charLength, result, i, t);
                        i += t;
                    } else {
                        result[i++] = '0';
                    }
                }
            } else if (decExponent <=0 && decExponent > -3) {
                result[i++] = '0';
                result[i++] = '.';
                if (decExponent != 0) {
                    System.arraycopy(zero, 0, result, i, -decExponent);
                    i -= decExponent;
                }
                System.arraycopy(digits, 0, result, i, nDigits);
                i += nDigits;
            } else {
                result[i++] = digits[0];
                result[i++] = '.';
                if (nDigits > 1) {
                    System.arraycopy(digits, 1, result, i, nDigits-1);
                    i += nDigits-1;
                } else {
                    result[i++] = '0';
                }
                result[i++] = 'E';
                int e;
                if (decExponent <= 0) {
                    result[i++] = '-';
                    e = -decExponent+1;
                } else {
                    e = decExponent-1;
                }
                // decExponent has 1, 2, or 3, digits
                if (e <= 9) {
                    result[i++] = (char)(e+'0');
                } else if (e <= 99) {
                    result[i++] = (char)(e/10 +'0');
                    result[i++] = (char)(e%10 + '0');
                } else {
                    result[i++] = (char)(e/100+'0');
                    e %= 100;
                    result[i++] = (char)(e/10+'0');
                    result[i++] = (char)(e%10 + '0');
                }
            }
        }
        return i;
    }
    
    0 讨论(0)
  • 2021-02-14 00:57

    Use BigDecimal to do that same calculation. (using doubles has precision problems because of its representation).

    • Construct it with new BigDecimal(String.valueOf(yourDouble)) (this is still going through string, but the parts are not separated via string manipulation)
    • use bd.subtract(new BigDecimal(bd.intValue()) to determine the fraction
    0 讨论(0)
  • 2021-02-14 01:00

    String doubleAsString = Double.toString(123.456);

    String beforeDecimal=doubleAsString.substring(0,doubleAsString.indexOf(".")); //123

    String afterDecimal=doubleAsString.substring(doubleAsString.indexOf(".")+1); //456

    0 讨论(0)
提交回复
热议问题