Подтвердить что ты не робот

Каков наилучший способ разделить двойное на две части "integer & fraction" в java

Я попытался отделить 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

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

4b9b3361

Ответ 1

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

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

Ответ 2

Вот еще одно решение, основанное на 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 или a? Должна быть хотя бы одна цифра, чтобы представлять дробную часть, а за ее пределами - столько же, сколько нужно, столько цифр, сколько необходимо для однозначного выделения значения аргумента из смежных значений типа 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;
}

Ответ 3

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

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]));

выход:

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)

Ответ 4

плохое решение (с использованием 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, поэтому мы действительно получаем десятичную точку)

Ответ 5

Строка doubleAsString = Double.toString(123.456);

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

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