BigDecimal и BigInteger
Класс java.math.BigDecimal предпочтительнее для работы с финансами вместо double, так как более гибок в настройках. Мне не приходилось использовать класс на практике. Но при создании бухгалтерского софта на него стоит обратить внимание.
Создать BigDecimal из числа типа double просто:
BigDecimal bigDecimal = new BigDecimal(1.5);
Можно создать из строки:
BigDecimal bigDecimal = new BigDecimal("1.5");
Чтобы задать количество цифр после запятой, используйте метод setScale(). Хорошей практикой является одновременное указание вместе с масштабом режима округления с помощью setScale(scale, roundingMode). Режим округления задаёт правило округления числа.
bigDecimal.setScale(2);
Есть восемь вариантов режима округления, которые задаются константами:
ROUND_CEILING: В большую сторону
0.333 -> 0.34 -0.333 -> -0.33
ROUND_DOWN: Отбрасывание разряда
0.333 -> 0.33 -0.333 -> -0.33
ROUND_FLOOR: В меньшую сторону
0.333 -> 0.33 -0.333 -> -0.34
ROUND_HALF_UP: Округление вверх, если число после запятой >= .5
0.5 -> 1.0 0.4 -> 0.0
ROUND_HALF_DOWN: Округление вверх, если число после запятой > .5
0.5 -> 0.0 0.6 -> 1.0
ROUND_HALF_EVEN: Округление половины по чётности округляет как обычно. Однако, когда округляемая цифра 5, округление будет идти вниз, если цифра слева от 5 чётная и вверх, если нечётная. Это лучше всего иллюстрируется примером:
BigDecimal a = new BigDecimal("2.5"); // цифра слева от 5 чётная, поэтому округление вниз BigDecimal b = new BigDecimal("1.5"); // цифра слева от 5 нечётная, поэтому округление вверх Log.i(TAG, a.setScale(0, BigDecimal.ROUND_HALF_EVEN).toString());// => 2 Log.i(TAG, b.setScale(0, BigDecimal.ROUND_HALF_EVEN).toString());// => 2
ROUND_UNNECESSARY: Используйте ROUND_UNNECESSARY когда необходимо использовать один из методов, который требует ввода режима округления, но известно, что результат округлять не надо.
При делении будьте осторожны и указывайте способ округления в методе divide(). В противном случае можно получить ArithmeticException.
Числа BigDecimal являются неизменными (как String). Это означает, что если создаётся новый объект BigDecimal со значением «2.00», такой объект останется «2.00» и никогда не может быть изменён.
Поэтому методы add(), multiply(), subtract(), divide(), mod() возвращают новый объект BigDecimal, содержащий результат.
Не используйте для сравнения метод equals(), потому что метод будет сравнивать масштабы. Если масштабы различаются. Вместо этого следует использовать методы compareTo() и signum().
Аналогично работайте с классом BigInteger.
BigInteger
Найти наибольшее число из двух при помощи метода max().
BigInteger firstNumber, secondNumber, maxNumber; firstNumber = new BigInteger("900"); secondNumber = new BigInteger("102"); maxNumber = firstNumber.max(secondNumber); System.out.println("The maximum value between " + firstNumber + " and " + secondNumber + " is " + maxNumber);
Как округлить 0.5 в меньшую сторону, а 0.5000001 — в большую
Для вашей задачи можно воспользоваться классом java.math.BigDecimal, который используется для более сложных ситуаций с округлением (вроде вашей).
Для решения вашей задачи:
- Создадим объект класса BigDecimal :
BigDecimal number = new BigDecimal("0.5");
- Задаём правило для округления. В примере 0 — количество цифр после запятой, ROUND_HALF_DOWN — то, округление которое вы и желали получить: если оба соседних числа равноудалены (то есть дробная часть 5 десятых), то округление произойдёт в сторону 0, если же числа не равноудалены, то округление произойдёт по принципу «где ближе».
number = number.setScale(0, ROUND_HALF_DOWN);
И так, что вышло в итоге:
BigDecimal number1 = new BigDecimal("0.5"); BigDecimal number2 = new BigDecimal("0.50000001"); number1 = number1.setScale(0, ROUND_HALF_DOWN); //0 number2 = number2.setScale(0, ROUND_HALF_DOWN); //1
Советы по работе с BigDecimal в Java
Если вы работаете на Java с дробными числами и при этом вам очень важно не потерять в точности, то использовать примитивные типы данных float и double нельзя. Они эту точность не гарантируют, и определяя число 1.01 вы на самом деле получите длинную дробную часть. Это связано с особенностями представления чисел с плавающей точкой.
Вместо них нужно использовать класс BigDecimal. Этот тип данных позволяет хранить сколь угодно большие значения и со сколь угодно большим количеством знаков после запятой (лишь бы хватило памяти). Поскольку это ссылочный тип, а не примитив, он реализован как неизменяемый, подобно классу String. То есть совершая любые математические операции над исходным экземпляром, вы каждый раз будете получать новый, не меняя при этом исходный.
Инициализация через строку
Вы можете создать экземпляр класса BigDecimal на основании числа или строки. При этом использовать значения float и double в качестве исходных опять-таки не рекомендуется.
System.out.println( new BigDecimal( 10 )); // 10
System.out.println( new BigDecimal( «10.1» )); // 10.1
System.out.println( new BigDecimal( 10.1 ));
// 10.0999999999999996447286321199499070644378662109375
Инициализация с помощью целых чисел не позволяет вам указать дробные значения. Поэтому универсальным способом инициализации BigDecimal является именно строка. Тогда вы получите ровно то число и с тем количеством знаков, которое вы указали.
Основные операции
Поскольку BigDecimal не является встроенным типом, то обычные операторы вроде «+» и «-» к нему уже не применимы. Вместо этого вам нужно использовать соответствующие методы add(), subtract(), multiply() и divide() соответственно.
var a = new BigDecimal( «6.000» );
var b = new BigDecimal( «3.0» );
System.out.println(a.add(b)); // 9.000
System.out.println(a.subtract(b)); // 3.000
System.out.println(a.multiply(b)); // 18.0000
System.out.println(a.divide(b)); // 2.00
Как уже говорилось выше, любые операции над a и b порождают новый экземпляр BigDecimal. При этом исходные экземпляры никак не меняются.
Указывайте точность при делении
При операциях деления нужно проявлять осторожность и обязательно указывать точность в явном виде. В случаях, когда результат деления представляет собой бесконечную дробь, BigDecimal мог бы израсходовать для представления всю доступную память, ведь он позволяет хранить любую точность. Поэтому если точность явно не указана и при делении возникает такая ситуация, то BigDecimal кидает исключение ArithmeticException. Ниже показаны оба варианта деления:
var a = new BigDecimal( «10.0» );
var b = new BigDecimal( «3.0» );
System.out.println(a.divide(b, 4 , RoundingMode. HALF_UP )); // явно указали точность — получим 3.3333
System.out.println(a.divide(b)); // не указали точность — получим ArithmeticException
Правильно будет всегда явно указывать количество знаков после запятой (в нашем случае 4 знака) и способ округления RoundingMode. Есть несколько разных способов округления, HALF_UP означает, что если на конце стоит 5, то округляем в большую сторону. То есть 0.05 округляется до 0.1, а 0.04 станет 0.0.
Явно указывайте точность результата
После выполнения последовательности математических операций нелишним будет сделать явное округление. Например, перед сохранением в БД. Так как даже если вы перемножаете два целых числа, у которых есть разная дробная часть, то в результате вы получите ещё бОльшую дробную часть. Ниже приведены варианты с округлением и без.
var a = new BigDecimal( «10.00» ); // 2 знака после запятой
var b = new BigDecimal( «20.000» ); // 3 знака
System.out.println(a.multiply(b)); // 200.00000 — 5 знаков
System.out.println(a.multiply(b).setScale( 2 , RoundingMode. HALF_UP )); // 200.00 — 2 знака
Поэтому явно указывайте точность результата с помощью метода setScale(). И опять-таки этот метод не меняет исходное значение, а порождает новый экземпляр класса.
Сравнивайте два значения с помощью метода compareTo()
В Java объекты на равенство принято сравнивать с помощью метода equals(). Однако в случае с BigDecimal так делать не стоит, поскольку equals() не учитывает точность. Для него числа 10 и 10.0 не равны. Для этих целей используйте метод compareTo(), который в случае равенства возвращает 0.
var a = new BigDecimal( «10» );
var b = new BigDecimal( «10.0» );
System.out.println(a.equals(b)); // false
System.out.println(a.compareTo(b) == 0 ); // true
Метод compareTo() позволяет проверять не только равенство, но и те случаи, когда одно значение больше или меньше другого. Если a > b – метод вернёт 1. Если a var a = new BigDecimal( «2.5» );
var b = new BigDecimal( «3» );
System.out.println(a.compareTo(b) == 0 ); // проверяем условие a == b, false
System.out.println(a.compareTo(b) > 0 ); // a > b, false
System.out.println(a.compareTo(b) < 0 ); // a < b, true
System.out.println(a.compareTo(b) >= 0 ); // a >= b, false
System.out.println(a.compareTo(b)
Преобразование в целое число с проверкой
Иногда может потребоваться преобразование в целое число из BigDecimal в int или long. Класс предоставляет два метода: intValue() и intValueExact(). Первый просто отбрасывает дробную часть – фактически, округляет «вниз». Второй метод вернёт целое число только если нет дробной части. Если же она имеется, то кинет исключение ArithmeticException.
var a = new BigDecimal( «100.00» );
System.out.println(a.intValue()); // 100
System.out.println(a.intValueExact()); // 100
var b = new BigDecimal( «200.5» );
System.out.println(b.intValue()); // 200
System.out.println(b.intValueExact()); // ArithmeticException
Выбирайте один из двух подходов в зависимости от ваших требований. Но скорее всего для контроля точности вы будете использовать intValueExact(). Такая же пара методов имеется и для других целочисленных типов (byte, short, long и даже BigInteger).
Преобразуйте в строку с помощью toPlainString()
Чтобы получить текстовое представление объекта, в Java обычно используют метод toString(). Этот же метод используется по умолчанию при выводе значения в консоль или в лог. Однако в BigDecimal для этих целей лучше использовать метод toPlainString(). Чем же он отличается от стандартного? В большинстве случае текстовое представления, возвращаемое этими методами совпадает. Но есть и различия. Рассмотрим пример.
var a = new BigDecimal( «100.01» );
System.out.println(a); // неявно вызывается toString(), 100.01
System.out.println(a.toPlainString()); // 100.01
var b = new BigDecimal( «0.0000000001» );
System.out.println(b); // toString() вернёт 1E-10
System.out.println(b.toPlainString()); // 0.0000000001
Когда значение очень маленькое и содержит большое количество ведущих нулей в дробной части, toString() возвращает «инженерное» представление числа «1E-10», что означает 10 в степени -10. Такой формат может сбить с толку, если вы отображаете это где-то на интерфейсе. Поэтому всегда используйте toPlainString(), чтобы исключить такую ситуацию.
Итоги
Мы рассмотрели несколько полезных советов при работе с классом BigDecimal. Этот класс универсален и позволяет хранить любые значения без ограничения на размер как целой, так и дробной части. При этом он расходует больше памяти, т.к. не является примитивом.
Инициализацию BigDecimal следует делать через текстовое представление числа. Любая операция над BigDecimal порождает новый экземпляр класса, а старый остаётся без изменений. При совершении математических операций нельзя использовать привычные операторы. Вместо них нужно использовать соответствующие методы. Кроме того, имеет смысл явно указывать желаемую точность результата, а при делении это делать обязательно. Для сравнения двух значений также нужно использовать специальный метод compareTo().
Если у Вас остались вопросы по работе с BigDecimal – смело пишите их в комментариях.
Изучаем округление в Java: как в Java округлить число до n знаков после запятой
В этой статье мы рассмотрим, как в Java округлить число до n десятичного знаков.
Десятичные числа в Java
Java предоставляет два примитивных типа, которые могут использоваться для хранения десятичных чисел: float и double . Double — это тип данных, используемый по умолчанию:
double PI = 3.1415
Но оба типа данных не должны использоваться для вычисления точных значений . Например, валютных котировок и округления чисел. Для этого лучше применять класс BigDecimal .
Форматирование десятичного числа
Если нужно вывести десятичное число с n знаками после запятой, можно отформатировать выходную строку:
System.out.printf("Value with 3 digits after decimal point %.3f %n", PI); // Вывод: Значения с 3 знаками после запятой 3.142
Также можно округлить значение с помощью класса DecimalFormat :
DecimalFormat df = new DecimalFormat("###.###"); System.out.println(df.format(PI));
Этот класс позволяет настроить процесс округления числа.
Java: округление Double с помощью BigDecimal
Чтобы округлить тип double до n знаков после запятой, можно написать helper-метод :
private static double round(double value, int places)
Обратите внимание, что при создании экземпляра класса BigDecimal мы должны всегда использовать конструктор BigDecimal(String) . Это позволяет избежать проблем с представлением неточных значений.
Можно сделать то же самое, используя библиотеку Apache Commons Math :
org.apache.commons commons-math3 3.5
Актуальную версию этой библиотеки можно найти здесь. Для округления чисел применяется метод Precision.round() , который принимает два аргумента — значение и масштаб:
Precision.round(PI, 3);
По умолчанию он использует тот же метод округления HALF_UP, что хэлпер. Поэтому результаты должны быть одинаковыми.
Кроме этого можно изменить процесс приведения чисел, передав в качестве третьего параметра необходимый метод округления.
Округление чисел с плавающей запятой с помощью DoubleRounder
DoubleRounder — это утилита из библиотеки decimal4j. Она предоставляет быстрый метод округления double чисел до 18 знаков после запятой.
Последнюю версию библиотеки можно найти здесь. Чтобы подключить ее, добавьте зависимость в файл pom.xml :
org.decimal4j decimal4j 1.0.3
Пример использования утилиты:
DoubleRounder.round(PI, 3);
Но DoubleRounder дает сбой в нескольких сценариях. Например:
System.out.println(DoubleRounder.round(256.025d, 2)); // OUTPUTS: 256.02 вместо ожидаемого 256.03
Метод Math.round() java
При использовании метода Math.round() можно контролировать п -количество десятичных разрядов путем умножения и деления на 10^п :
public static double roundAvoid(double value, int places) < double scale = Math.pow(10, places); return Math.round(value * scale) / scale; >
Этот метод не рекомендуется использовать для округления чисел, поскольку он усекает значение . Во многих случаях значения округляются неправильно:
System.out.println(roundAvoid(1000.0d, 17)); // Вывод: 92.23372036854776 !! System.out.println(roundAvoid(260.775d, 2)); // Вывод: 260.77 вместо ожидаемого 260.78
Заключение
В этой статье мы рассмотрели различные методы округления чисел до n знаков после запятой, доступные в Java.
Можно просто отформатировать вывод без изменения значения или округлить переменную с помощью вспомогательного метода или подключаемых библиотек.
Код, использованный в этой статье, доступен на GitHub.