Java округляется с кучей девяток в конце - PullRequest
2 голосов
/ 13 февраля 2012

Похоже, что BigDecimal.setScale усекает до десятичной позиции масштаба + 1, а затем округляет только на основе этого десятичного знака. Это нормально или есть чистый способ применить режим округления к каждому десятичному числу?

Это выводит: 0,0697 (это НЕ режим округления, которому меня учили в школе)

double d = 0.06974999999;
BigDecimal bd = BigDecimal.valueOf(d);
bd = bd.setScale(4, RoundingMode.HALF_UP);
System.out.println(bd);


Это выводит: 0.0698 (это режим округления, которому меня учили в школе)

double d = 0.0697444445;
BigDecimal bd = BigDecimal.valueOf(d);
int scale = bd.scale();
while (4 < scale) {
    bd = bd.setScale(--scale, RoundingMode.HALF_UP);
}
System.out.println(bd);

EDITED Прочитав некоторые ответы, я понял, что все испортил. Я был немного расстроен, когда написал свой вопрос. Итак, я собираюсь переписать причину вопроса, хотя ответы мне очень помогли, мне все еще нужен совет.

Проблема в следующем: Мне нужно округлить 0,06974999999 до 0,0698, потому что я знаю, что эти многие десятичные дроби на самом деле означают 0,6975 (ошибка округления в месте, которое не находится под моим контролем).
Так что я играл с неким «двойным округлением», при котором округление выполняется в два этапа: сначала округление до некоторой более высокой точности, затем округление до необходимой точности.
(Здесь я запутался, потому что думал, что цикл для каждого десятичного знака будет безопаснее). Дело в том, что я не знаю, к какой более высокой точности округлить на первом шаге (я использую число десятичных дробей-1). Также я не знаю, смогу ли я найти неожиданные результаты для других случаев.
Вот первый способ, которым я отказался в пользу того, с циклом, который теперь выглядит намного лучше после прочтения ваших ответов:

public static BigDecimal getBigDecimal(double value, int decimals) {
    BigDecimal bd = BigDecimal.valueOf(value);
    int scale = bd.scale();
    if (scale - decimals > 1) {
        bd = bd.setScale(scale - 1, RoundingMode.HALF_UP);
    }
    return bd.setScale(decimals, roundingMode.HALF_UP);
}

Печать следующих результатов:
0.0697444445 = 0.0697
0.0697499994 = 0.0697
0,0697499995 = 0,0698
0,0697499999 = 0,0698
0.0697444445 = 0.069744445 // округлено до 9 десятичных знаков
0.0697499999 = 0.069750000 // округлено до 9 десятичных знаков
0,069749 = 0,0698

Теперь возникает вопрос: есть ли лучший способ сделать это (возможно, другой режим округления)? а если это безопасно использовать в качестве общего метода округления?
Мне нужно округлить многие значения, и мне приходится выбирать между этим и стандартным подходом во время выполнения, в зависимости от того, какие числа я получаю, и это кажется действительно сложным.

Еще раз спасибо за ваше время.

Ответы [ 4 ]

0 голосов
/ 14 февраля 2012

А как насчет того, чтобы проверить предыдущие и следующие значения, чтобы увидеть, уменьшают ли они масштаб?

public static BigDecimal getBigDecimal(double value) {
    BigDecimal bd = BigDecimal.valueOf(value);
    BigDecimal next = BigDecimal.valueOf(Math.nextAfter(value, Double.POSITIVE_INFINITY));
    if (next.scale() < bd.scale()) {
        return next;
    }
    next = BigDecimal.valueOf(Math.nextAfter(value, Double.NEGATIVE_INFINITY));
    if (next.scale() < bd.scale()) {
        return next;
    }
    return bd;
}

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

0 голосов
/ 13 февраля 2012

При округлении вы смотрите на значение, которое следует за последней цифрой, к которой вы округляете, в первом примере вы округляете 0.06974999999 до 4 десятичных знаков.Таким образом, у вас есть 0.0697, а затем 4999999 (или, по сути, 697.4999999).Так как режим округления HALF_UP, 0,499999 меньше 0,5, поэтому он округляется в меньшую сторону.

0 голосов
/ 13 февраля 2012

Если разница между 0.06974999999 и 0.06975 так важна, вам следует перейти на BigDecimals немного раньше.По крайней мере, если производительность так важна, придумайте, как использовать long и int.Double и float не для людей, которые могут отличить 1,0 от 0,999999999999999.Когда вы их используете, информация теряется, и нет определенного способа ее восстановить.

(Эта информация, мягко говоря, может показаться незначительной, но если путешествие в 1 000 000 ярдов ставит вас на вершину утеса, то вы путешествуете1 000 001 ярдов приведут вас в ярд мимо вершины утеса. Этот последний ярд имеет значение. И если вы потеряете 1 копейку на миллиард долларов, у вас будут еще большие проблемы, когда бухгалтеры получат послевы.)

0 голосов
/ 13 февраля 2012

Если вам нужно сместить округление, вы можете добавить небольшой коэффициент.

, например, округлить до 6 десятичных знаков.

double d = 
double rounded = (long) (d * 1000000 + 0.5) / 1e6;

, чтобы добавить небольшой коэффициент, который вам нужно решитьсколько дополнительных вы хотите дать.например,

double d = 
double rounded = (long) (d * 1000000 + 0.50000001) / 1e6;

например,

public static void main(String... args) throws IOException {
    double d1 = 0.0697499994;
    double r1 = roundTo4places(d1);
    double d2 = 0.0697499995;
    double r2= roundTo4places(d2);
    System.out.println(d1 + " => " + r1);
    System.out.println(d2 + " => " + r2);

}

public static double roundTo4places(double d) {
    return (long) (d * 10000 + 0.500005) / 1e4;
}

отпечатки

0.0697499994 => 0.0697
0.0697499995 => 0.0698

Первый правильный.

0.44444444 ... 44445округляется до целого числа 0,0

только 0,500000000 ... 000 или более округляется до 1,0

Нет режима округления, который округляет 0,4 и 0,45.

Если вы думаете об этом, вам нужен равный шанс, что случайное число будет округлено в большую или меньшую сторону.Если вы суммируете достаточно большое количество случайных чисел, ошибка, созданная округлением, отменяется.


Раунд с половиной округляется так же, как

long n = (long) (d + 0.5);

Рекомендуемое округление

long n = (long) (d + 5.0/9);

Random r = new Random(0);
int count = 10000000;

// round using half up.
long total = 0, total2 = 0;
for (int i = 0; i < count; i++) {
    double d = r.nextDouble();
    int rounded = (int) (d + 0.5);
    total += rounded;

    BigDecimal bd = BigDecimal.valueOf(d);
    int scale = bd.scale();
    while (0 < scale) {
        bd = bd.setScale(--scale, RoundingMode.HALF_UP);
    }

    int rounded2 = bd.intValue();
    total2 += rounded2;
}
System.out.printf("The expected total of %,d rounded random values is %,d,%n\tthe actual total was %,d, using the biased rounding %,d%n",
        count, count / 2, total, total2);

отпечатков

The expected total of 10,000,000 rounded random values is 5,000,000, 
    the actual total was 4,999,646, using the biased rounding 5,555,106

http://en.wikipedia.org/wiki/Rounding#Round_half_up

...