20 июля 2022
Тэги: Java, руководство.
Если вы работаете на Java с дробными числами и при этом вам очень важно не потерять в точности, то использовать примитивные типы данных float и double нельзя. Они эту точность не гарантируют, и определяя число 1.01 вы на самом деле получите длинную дробную часть. Это связано с особенностями представления чисел с плавающей точкой.
Вместо них нужно использовать класс BigDecimal. Этот тип данных позволяет хранить сколь угодно большие значения и со сколь угодно большим количеством знаков после запятой (лишь бы хватило памяти). Поскольку это ссылочный тип, а не примитив, он реализован как неизменяемый, подобно классу String. То есть совершая любые математические операции над исходным экземпляром, вы каждый раз будете получать новый, не меняя при этом исходный.
Вы можете создать экземпляр класса BigDecimal на основании числа или строки. При этом использовать значения float и double в качестве исходных опять-таки не рекомендуется.
Инициализация с помощью целых чисел не позволяет вам указать дробные значения. Поэтому универсальным способом инициализации BigDecimal является именно строка. Тогда вы получите ровно то число и с тем количеством знаков, которое вы указали.
Поскольку BigDecimal не является встроенным типом, то обычные операторы вроде «+» и «-» к нему уже не применимы. Вместо этого вам нужно использовать соответствующие методы add(), subtract(), multiply() и divide() соответственно.
Как уже говорилось выше, любые операции над a и b порождают новый экземпляр BigDecimal. При этом исходные экземпляры никак не меняются.
При операциях деления нужно проявлять осторожность и обязательно указывать точность в явном виде. В случаях, когда результат деления представляет собой бесконечную дробь, BigDecimal мог бы израсходовать для представления всю доступную память, ведь он позволяет хранить любую точность. Поэтому если точность явно не указана и при делении возникает такая ситуация, то BigDecimal кидает исключение ArithmeticException. Ниже показаны оба варианта деления:
Правильно будет всегда явно указывать количество знаков после запятой (в нашем случае 4 знака) и способ округления RoundingMode. Есть несколько разных способов округления, HALF_UP означает, что если на конце стоит 5, то округляем в большую сторону. То есть 0.05 округляется до 0.1, а 0.04 станет 0.0.
После выполнения последовательности математических операций нелишним будет сделать явное округление. Например, перед сохранением в БД. Так как даже если вы перемножаете два целых числа, у которых есть разная дробная часть, то в результате вы получите ещё бОльшую дробную часть. Ниже приведены варианты с округлением и без.
Поэтому явно указывайте точность результата с помощью метода setScale(). И опять-таки этот метод не меняет исходное значение, а порождает новый экземпляр класса.
В Java объекты на равенство принято сравнивать с помощью метода equals(). Однако в случае с BigDecimal так делать не стоит, поскольку equals() не учитывает точность. Для него числа 10 и 10.0 не равны. Для этих целей используйте метод compareTo(), который в случае равенства возвращает 0.
Метод compareTo() позволяет проверять не только равенство, но и те случаи, когда одно значение больше или меньше другого. Если a > b – метод вернёт 1. Если a < b – метод вернёт -1. На первый взгляд это кажется неочевидным. Однако предлагаю такое эмпирическое правило: тот знак, который вы хотели бы поставить между a и b, ставьте между результатом compareTo() и нулём.
Иногда может потребоваться преобразование в целое число из BigDecimal в int или long. Класс предоставляет два метода: intValue() и intValueExact(). Первый просто отбрасывает дробную часть – фактически, округляет «вниз». Второй метод вернёт целое число только если нет дробной части. Если же она имеется, то кинет исключение ArithmeticException.
Выбирайте один из двух подходов в зависимости от ваших требований. Но скорее всего для контроля точности вы будете использовать intValueExact(). Такая же пара методов имеется и для других целочисленных типов (byte, short, long и даже BigInteger).
Чтобы получить текстовое представление объекта, в Java обычно используют метод toString(). Этот же метод используется по умолчанию при выводе значения в консоль или в лог. Однако в BigDecimal для этих целей лучше использовать метод toPlainString(). Чем же он отличается от стандартного? В большинстве случае текстовое представления, возвращаемое этими методами совпадает. Но есть и различия. Рассмотрим пример.
Когда значение очень маленькое и содержит большое количество ведущих нулей в дробной части, toString() возвращает «инженерное» представление числа «1E-10», что означает 10 в степени -10. Такой формат может сбить с толку, если вы отображаете это где-то на интерфейсе. Поэтому всегда используйте toPlainString(), чтобы исключить такую ситуацию.
Мы рассмотрели несколько полезных советов при работе с классом BigDecimal. Этот класс универсален и позволяет хранить любые значения без ограничения на размер как целой, так и дробной части. При этом он расходует больше памяти, т.к. не является примитивом.
Инициализацию BigDecimal следует делать через текстовое представление числа. Любая операция над BigDecimal порождает новый экземпляр класса, а старый остаётся без изменений. При совершении математических операций нельзя использовать привычные операторы. Вместо них нужно использовать соответствующие методы. Кроме того, имеет смысл явно указывать желаемую точность результата, а при делении это делать обязательно. Для сравнения двух значений также нужно использовать специальный метод compareTo().
Если у Вас остались вопросы по работе с BigDecimal – смело пишите их в комментариях.
Kotlin, Java, Spring, Spring Boot, Spring Data, SQL, PostgreSQL, Oracle, Linux, Hibernate, Collections, Stream API, многопоточность, файлы, Nginx, Apache, maven, gradle, JUnit, YouTube, новости, руководство, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml.
26.04.2024 21:57 1
Его нельзя перезаписать, тогда как же его использовать в циклах?
26.04.2024 22:21 devmark
Точно так же, как и строки - всегда помнить про неизменяемость таких объектов и надеяться на своевременную сборку мусора.