Каков наилучший способ разделить double на две части "целое число и дробь" в Java - PullRequest
12 голосов
/ 19 мая 2011

Я попытался отделить 5.6 (например) следующим способом:

private static double[] method(double d)
{
    int integerPart = 0;
    double fractionPart = 0.0;
    integerPart = (int) d;
    fractionPart = d - integerPart;
    return new double[]{integerPart, fractionPart};
}

Но то, что я получил, это:

[0] = 5.0
[1] = 0.5999999999999996

Есть ли у вас какие-либо предложения сделать это без преобразования числа в строку?

Ответы [ 5 ]

12 голосов
/ 19 мая 2011

Используйте BigDecimal для того же расчета. (использование double имеет проблемы с точностью из-за его представления).

  • Создайте его с помощью new BigDecimal(String.valueOf(yourDouble)) (это все еще проходит через строку, но части не разделяются посредством манипулирования строкой)
  • используйте bd.subtract(new BigDecimal(bd.intValue()) для определения доли
5 голосов
/ 19 мая 2011

Вот еще одно решение, основанное на BigDecimal (которое не проходит через String).

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

Как вы заметите, вы все равно не получите 0.6 каквывод для дробной части.(Вы даже не можете хранить 0.6 в double!) Это связано с тем, что математическое, действительное число 5.6 на самом деле не представляется в виде двойного числа в точности как 5.6, а как 5.599999 ...


Вы также можете сделать

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

, который на самом деле дает [5.0, 0.6].

* BigDecimal.valueOf в большинстве JDK (внутренне) реализовано посредством вызова Double.toString однако.Но по крайней мере связанные со строками вещи не загромождают ваш код: -)


Хороший дополнительный вопрос в комментарии:

Если он представлен как 5.599999999 ..., то почему Double.toString(5.6) дает ровно "5.6"

Метод Double.toString на самом деле очень сложный .Из документации Double.toString:

[...]

Сколько цифр должно быть напечатано для дробной части m илистоит?Должна быть хотя бы одна цифра для представления дробной части, и помимо этого столько, но только столько, больше цифр, сколько необходимо для уникального различия значения аргумента от смежных значений типа double. То есть предположим, чтоx - точное математическое значение, представленное десятичным представлением, полученным этим методом для конечного ненулевого аргумента d.Тогда d должно быть двойным значением, ближайшим к x;или если два двойных значения одинаково близки к x, то d должно быть одним из них, а младший значащий бит значенияи d должен быть 0.

[...]

Код для получения символов "5.6" сводится к 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;
}
1 голос
/ 19 мая 2011

Чтобы увидеть, что происходит, взгляните на двоичные представления чисел:

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 равно 1.4 * 2 2, но 0,6 - 1,2 * 2 -1 .Поскольку показатель имеет меньший показатель, нормализация приводит к смещению мантиссы на три бита влево.Тот факт, что повторяющиеся члены (..66666..) изначально были приближением дроби 7/5, был забыт, а недостающие биты заменены нулями.

Учитывая исходное значение double в качестве входных данных дляВаш метод, нет способа избежать этого.Чтобы сохранить точное значение, вам нужно использовать формат, который точно представляет желаемое значение, например Fraction из Apache commons-math.(Для этого конкретного примера с d=5.6 a BigDecimal также сможет точно представить его, но есть другие числа, которые он не может представить точно, например 4/3)

0 голосов
/ 22 октября 2014

String doubleAsString = Double.toString (123.456);

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

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

0 голосов
/ 19 мая 2011

Решение для бедняков (с использованием 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))
        };
    }

(локаль, поэтому мы действительно получаем десятичную точку )

...