GitHub 19k Star Путь Java-инженера к тому, чтобы стать богом, не приходите и не узнайте об этом!
GitHub 19k Star Путь Java-инженера к тому, чтобы стать богом, разве вы не хотите узнать об этом!
BigDecimal, я полагаю, что он знаком многим людям, и многие люди знают, как он используется.Это тип, представленный в пакете java.math, который можно использовать для точных операций.
Многие знают, что в таких сценариях, как представление суммы и вычисление суммы, нельзя использовать такие типы, как double и float, но следует использовать BigDecimal с большей поддержкой точности.
Поэтому во многих платежных, электронной коммерции, финансах и других сферах бизнеса BigDecimal используется очень часто.Однако, если вы ошибочно думаете, что пока вы используете BigDecimal для представления чисел, результат должен быть точным, то вы ошибаетесь!
В предыдущей статье мы рассказали, что использование метода equals в BigDecimal не проверяет, действительно ли два числа равны (Почему Alibaba запрещает использование метода equals BigDecimal для сравнения на равенство?).
Кроме этой ситуации, первым шагом в использовании BigDecimal является создание объекта BigDecimal.Если на этом шаге возникнет проблема, то расчет будет неверным!
Итак, как правильно создать BigDecimal?
Что касается этой проблемы, я просмотрел много кода и взял интервью у многих разработчиков, работающих на переднем крае, и многие люди попали в яму. Это легко упускаемый из виду, но важный вопрос.
По этому вопросу в «Руководстве по разработке Java для Alibaba» есть предложение или требование:
Это [обязательное] предложение, так в чем же причина этого?
Для того, чтобы понять эту проблему, нам в основном необходимо прояснить следующие вопросы:
1. Почему двойное число неточно? 2. Как гарантируется точность BigDecimal?
Зная ответы на эти два вопроса, мы, вероятно, поняли, почему мы не можем использовать BigDecimal(double) для создания BigDecimal.
Почему двойная неточность
Во-первых,Компьютеры понимают только двоичные, то есть 0 и 1, это должен знать каждый.
Затем все числа, включая целые и десятичные, необходимо преобразовать в двоичные, если они хотят храниться и отображаться на компьютере.
Преобразование десятичных целых чисел в двоичные очень просто. Обычно "разделите на 2, возьмите остаток и расположите в обратном порядке". Например, двоичное число 10 равно 1010.
Однако как представить десятичные числа в двоичном формате?
При преобразовании десятичных дробей в двоичные обычно используется метод «умножения на 2, округления, упорядочения», например, 0,625 преобразуется в двоичное число и выражается как 0,101.
Однако не все десятичные числа могут быть преобразованы в двоичные, например, 0,1 не может быть напрямую представлено в двоичном виде Его двоичное число равно 0,000110011001100... Это десятичное число с бесконечным циклом.
Следовательно, компьютер не может точно представить 0,1 в двоичном формате. Другими словами, в компьютерах многие десятичные числа не могут быть точно представлены в двоичном формате.
Что ж, эту проблему надо решать. Так,Люди придумали способ представления десятичной дроби с помощью аппроксимации с определенной точностью.. Это основная идея спецификации IEEE 754 (Стандарт IEEE для двоичной арифметики с плавающей запятой).
IEEE 754 определяет различные способы представления значений с плавающей запятой, наиболее часто используемыми являются 32-битные числа с плавающей запятой одинарной точности и 64-битные числа с плавающей запятой двойной точности.
В Java float и double используются для представления чисел с плавающей запятой одинарной точности и чисел с плавающей запятой двойной точности соответственно.
Так называемую различную точность можно просто понимать как разницу в количестве сохраняемых значащих цифр. Аппроксимация десятичных дробей таким образом, чтобы сохранить значащие цифры.
Итак, все знают, почемуДесятичное число, представленное двойным числом, является неточным.
Далее, вернемся к знакомству с BigDecimal, давайте посмотрим, как представлять число, как это обеспечивает точность?
Как точно считается BigDecimal?
Если вы видели исходный код BigDecimal, вы действительно можете найти это,Фактически, BigDecimal представляет число через «немасштабированное значение» и «масштаб».
В BigDecimal масштаб представлен полем масштаба.
Представление безмасштабных значений более сложное. Когда немасштабированное значение превышает пороговое значение (Long.MAX_VALUE по умолчанию), поле intVal используется для хранения немасштабированного значения, а поле intCompact используется для хранения Long.MIN_VALUE. В противном случае немасштабированное значение сжимается и сохраняется в длинном Поле типа intCompact для последующих вычислений.IntVal пусто.
Задействованные поля следующие:
public class BigDecimal extends Number implements Comparable<BigDecimal> {
private final BigInteger intVal;
private final int scale;
private final transient long intCompact;
}
Вы можете понять механизм сжатия безмасштабных значений.Это не является предметом этой статьи.Вам нужно только знать, что BigDecimal в основном представлен безмасштабным значением и масштабом.
Так что же такое шкала?
В дополнение к полю масштаба в BigDecimal также предоставляется метод scale() для возврата масштаба этого BigDecimal.
/**
* Returns the <i>scale</i> of this {@code BigDecimal}. If zero
* or positive, the scale is the number of digits to the right of
* the decimal point. If negative, the unscaled value of the
* number is multiplied by ten to the power of the negation of the
* scale. For example, a scale of {@code -3} means the unscaled
* value is multiplied by 1000.
*
* @return the scale of this {@code BigDecimal}.
*/
public int scale() {
return scale;
}
Итак, что означает масштаб?На самом деле, приведенный выше комментарий ясно дал понять:
Если шкала равна нулю или положительна, значение представляет количество цифр справа от десятичной точки для этого числа. Если масштаб отрицательный, истинное значение числа необходимо умножить на 10 в степени абсолютного значения отрицательного числа. Например, если масштаб равен -3, число нужно умножить на 1000, то есть в конце будет 3 нуля.
Например, 123,123, если он представлен BigDecimal, то его немасштабированное значение равно 123123, а его масштаб равен 3.
И 0,1, которое не может быть представлено в двоичном виде, может быть представлено с помощью BigDecimal, а также может быть представлено немасштабированным значением 1 и масштабом 1.
Все мы знаем, что для создания объекта нам нужно использовать конструктор класса, в BigDecimal есть следующие четыре конструктора:
BigDecimal(int)
BigDecimal(double)
BigDecimal(long)
BigDecimal(String)
Масштаб BigDecimal, созданный четырьмя вышеуказанными методами, отличается.
Среди них BigDecimal(int) и BigDecimal(long) относительно просты, поскольку все они являются целыми числами, поэтому все их шкалы равны 0.
Масштаб BigDecimal(double) и BigDecimal(String) имеет много знаний.
Что не так с BigDecimal(double)
BigDecimal предоставляет метод для создания BigDecimal через double — BigDecimal(double), но в то же время он также оставляет нам яму!
Поскольку мы знаем, что десятичное число, представленное двойным числом, является неточным, например число 0,1, двойное число может представлять только его приблизительное значение.
так,Когда мы используем new BigDecimal(0.1) для создания BigDecimal, созданное значение не точно равно 0,1.
Вместо 0,1000000000000000055511151231257827021181583404541015625. Это связано с тем, что сам доуль представляет собой лишь приближение.
Итак, если мы используем BigDecimal(double) для создания BigDecimal в нашем коде, мы теряем точность, что очень серьезно.
Создано с использованием BigDecimal(String)
Итак, как создать точный BigDecimal для представления десятичных знаков, ответ заключается в использовании String для создания.
Для BigDecimal(String), когда мы используем new BigDecimal("0.1") для создания BigDecimal, созданное значение точно равно 0,1.
Тогда его шкала также равна 1.
Но следует отметить, что шкалы new BigDecimal("0,10000") и new BigDecimal("0,1") соответственно равны 5 и 1. Если вы используете метод equals BigDecimal для сравнения, результат будет ложным. Конкретная причина и ссылка на решениеПочему Alibaba запрещает использование метода equals BigDecimal для сравнения на равенство?
Итак, чтобы создать BigDecimal, точно представляющий 0,1, используйте следующие два метода:
BigDecimal recommend1 = new BigDecimal("0.1");
BigDecimal recommend2 = BigDecimal.valueOf(0.1);
Здесь оставьте вопрос для размышления, BigDecimal.valueOf() реализуется путем вызова метода Double.toString, тогда, поскольку двойные значения неточны, как BigDecimal.valueOf(0.1) может быть точным?
Суммировать
Поскольку компьютер использует двоичный код для обработки данных, но многие десятичные числа, такие как двоичный код 0,1, являются беспроводным циклическим десятичным числом, и это число не может быть точно представлено в компьютере.
Поэтому люди используют приблизительный способ представления их в компьютере, поэтому существуют числа с плавающей запятой одинарной точности и числа с плавающей запятой двойной точности.
Следовательно, float как число с плавающей запятой одинарной точности и double как число с плавающей запятой двойной точности являются только приблизительными значениями при представлении десятичных дробей, а не действительными значениями.
Итак, при использовании BigDecimal(Double) для его создания результирующий BigDecimal теряет точность.
Вычисление с использованием числа, которое теряет точность, также дает неточные результаты.
Чтобы избежать этой проблемы, вы можете создать BigDecimal с помощью BigDecimal(String), в этом случае 0,1 будет представлено точно.
Его представление представляет собой комбинацию немасштабированного значения 1 и шкалы 1.
Об авторе:Hollis, человек с уникальным увлечением программированием, технический эксперт Alibaba, соавтор «Трех курсов для программистов» и автор серии статей «Дорога к Java-инженерам».
Если у вас есть комментарии, предложения или вы хотите пообщаться с автором, вы можете подписаться на официальный аккаунт [Hollis], оставьте мне сообщение прямо в фоновом режиме.