Использование java.math.MathContext

Недавно я пытался понять, как использовать java.math.MathContext, но не смог правильно понять. Используется ли он для округления в java.math.BigDecimal. Если да, то почему он не округляет десятичные цифры, а даже часть мантиссы.

Из документов API я узнал, что он соответствует стандарту, указанному в спецификациях ANSI X3.274-1996 и ANSI X3.274-1996/AM 1-2000, но я не читал их в Интернете.

Пожалуйста, дайте мне знать, если у вас есть идеи по этому поводу.


person jatanp    schedule 11.08.2008    source источник


Ответы (5)


@джатан

Спасибо за ответ. Это имеет смысл. Не могли бы вы объяснить мне MathContext в контексте метода BigDecimal#round.

В BigDecimal.round() по сравнению с любым другим BigDecimal методом нет ничего особенного. Во всех случаях MathContext указывает количество значащих цифр и метод округления. По сути, каждый MathContext. Есть точность, а есть RoundingMode.

Точность снова указывает количество значащих цифр. Поэтому, если вы укажете 123 в качестве числа и запросите две значащие цифры, вы получите 120. Это может быть яснее, если вы думаете с точки зрения научной нотации.

123 будет 1.23e2 в экспоненциальном представлении. Если вы сохраните только 2 значащие цифры, вы получите 1.2e2 или 120. Уменьшая количество значащих цифр, мы уменьшаем точность, с которой мы можем указать число.

Часть RoundingMode указывает, как мы должны справляться с потерей точности. Чтобы повторно использовать пример, если вы используете 123 в качестве числа и запрашиваете 2 значащие цифры, вы снижаете свою точность. При RoundingMode из HALF_UP (режим по умолчанию) 123 станет 120. С RoundingMode из CEILING вы получите 130.

Например:

System.out.println(new BigDecimal("123.4",
                   new MathContext(4,RoundingMode.HALF_UP)));
System.out.println(new BigDecimal("123.4",
                   new MathContext(2,RoundingMode.HALF_UP)));
System.out.println(new BigDecimal("123.4",
                   new MathContext(2,RoundingMode.CEILING)));
System.out.println(new BigDecimal("123.4",
                   new MathContext(1,RoundingMode.CEILING)));

Выходы:

123.4
1.2E+2
1.3E+2
2E+2

Вы можете видеть, что и точность, и режим округления влияют на результат.

person Derek Park    schedule 11.08.2008
comment
Есть ли способ отформатировать 123.4 с использованием двух значащих цифр, чтобы получить строку, подобную 120? Это 2 значащие цифры (как и 1.2E2), потому что конечные нули обычно не включаются в поиск значащих цифр, если нет конечного десятичного числа. И для моих целей, если число, которое нужно отформатировать, было 103.4, меня не волнует, что вы не можете сказать, что в результирующем числе 100 есть 2 значных фига. Я просто хочу более простой/чистый просмотр чисел. - person hepcat72; 07.02.2017

Для округления только дробной части BigDecimal воспользуйтесь методом BigDecimal.setScale(int newScale, int roundingMode).

Например. чтобы изменить число с тремя цифрами после запятой на число с двумя цифрами и округлением вверх:

BigDecimal original = new BigDecimal("1.235");
BigDecimal scaled = original.setScale(2, BigDecimal.ROUND_HALF_UP);

Результатом этого является BigDecimal со значением 1,24 (из-за правила округления).

person Øystein Øvrebø    schedule 16.11.2010
comment
Это ответ, которого хотят люди, работая с фиксированными десятичными числами и сохраняя фиксированное количество знаков после запятой. Не знаю, почему Sun испортила здесь «точность» в стиле с плавающей запятой, но это то, что такое MathContext. - person Thomas W; 28.04.2013
comment
Это работает даже для округления BigDecimal без какой-либо дробной части: original.setScale(0, RoundingMode.HALF_UP); - person Tobias Otto; 20.01.2021

Я бы добавил сюда несколько примеров. Я не нашел их в предыдущих ответах, но считаю их полезными для тех, кто может ввести в заблуждение значащие цифры с количеством десятичных знаков. Предположим, у нас есть такой контекст:

MathContext MATH_CTX = new MathContext(3, RoundingMode.HALF_UP);

Для этого кода:

BigDecimal d1 = new BigDecimal(1234.4, MATH_CTX);
System.out.println(d1);

совершенно ясно, что ваш результат 1.23E+3, как сказали ребята выше. Первые значащие цифры 123...

Но что в этом случае:

BigDecimal d2 = new BigDecimal(0.000000454770054, MATH_CTX);
System.out.println(d2);

ваше число не будет округлено до 3-х знаков после запятой - для кого-то это может быть не интуитивно понятным и заслуживающим внимания. Вместо этого оно будет округлено до первых трех значащих цифр, в данном случае это «4 5 4». Таким образом, приведенный выше код приводит к 4.55E-7, а не к 0.000, как можно было бы ожидать.

Похожие примеры:

BigDecimal d3 = new BigDecimal(0.001000045477, MATH_CTX);
 System.out.println(d3);  // 0.00100

BigDecimal d4 = new BigDecimal(0.200000477, MATH_CTX);
 System.out.println(d4);   // 0.200

BigDecimal d5 = new BigDecimal(0.000000004, MATH_CTX);
    System.out.println(d5); //4.00E-9

Я надеюсь, что этот очевидный, но актуальный пример будет полезен...

person guitar_freak    schedule 10.04.2014

Если я правильно вас понимаю, похоже, вы ожидаете, что MathContext будет контролировать, сколько цифр должно храниться после десятичной точки. Это не то, для чего это нужно. Он указывает, сколько цифр нужно сохранить, всего. Поэтому, если вы укажете, что вам нужны 3 значащие цифры, это все, что вы получите.

Например, это:

System.out.println(new BigDecimal("1234567890.123456789",
                   new MathContext(20)));

System.out.println(new BigDecimal("1234567890.123456789",
                   new MathContext(10)));

System.out.println(new BigDecimal("1234567890.123456789",
                   new MathContext(5)));

выведет:

1234567890.123456789
1234567890
1.2346E+9
person Derek Park    schedule 11.08.2008
comment
Спасибо. Как мне выбрать, сколько цифр оставить после запятой? - person Drew; 08.03.2012
comment
На самом деле это не вопрос для BigDecimal. Вместо этого с BigDecimal вы должны указать количество значащих цифр. Сколько цифр после запятой нужно определить при форматировании для отображения. Вы можете контролировать это с помощью String.format() или DecimalFormat.format(). - person Derek Park; 09.03.2012
comment
@Drew Я думаю, вы можете использовать BigDecimal.setScale для него. Первый параметр (масштаб) указывает, сколько чисел вы хотите сохранить после десятичной точки, а второй параметр (roundingMode) указывает желаемое поведение округления. - person Quazi Irfan; 21.01.2018
comment
@Drew Вот ответ, который объясняет это более подробно. - person Quazi Irfan; 21.01.2018

Это не для развлечения. На самом деле я нашел в Интернете пример, в котором говорилось об использовании MathContext для округления сумм/чисел, хранящихся в BigDecimal.

Например,

Если MathContext настроен на precision = 2 и rounding mode = ROUND_HALF_EVEN

BigDecimal Number = 0.5294, округляется до 0,53.

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

Например,

Number = 1.5294 округляется до 1.5

Number = 10.5294 округляется до 10

Number = 101.5294 округляется до 100

.... и так далее

Так что это не то поведение, которое я ожидал для округления (поскольку точность = 2).

Кажется, у него есть некоторая логика, потому что из скороговорки я могу сказать, что он берет первые две цифры (поскольку точность равна 2) числа, а затем добавляет 0 до нет. число цифр становится таким же, как и неокругленная сумма (ознакомьтесь с примером 101,5294...)

person jatanp    schedule 11.08.2008
comment
так что вы можете предложить вместо решения? - person Sumit Ramteke; 19.02.2014