Сохранение точности с помощью double в Java
public class doublePrecision {
public static void main(String[] args) {
double total = 0;
total += 5.6;
total += 5.8;
System.out.println(total);
}
}
Приведенный выше код выводит:
11.399999999999
Как бы мне заставить это просто распечатать (или иметь возможность использовать как) 11.4?
Переведено автоматически
Ответ 1
Как упоминали другие, вы, вероятно, захотите использовать класс BigDecimal
, если хотите иметь точное представление 11.4.
Теперь небольшое объяснение, почему это происходит:
Примитивные типы float
и double
в Java представляют собой числа с плавающей запятой, где число хранится в виде двоичного представления дроби и экспоненты.
Более конкретно, значение с плавающей запятой двойной точности, такое как double
тип, является 64-разрядным значением, где:
- 1 бит обозначает знак (положительный или отрицательный).
- 11 бит для показателя степени.
- 52 бита для значащих цифр (дробная часть в виде двоичного файла).
Эти части объединяются для получения double
представления значения.
(Источник: Википедия: Двойная точность)
Подробное описание того, как обрабатываются значения с плавающей запятой в Java, смотрите в разделе 4.2.3: Типы, форматы и значения с плавающей запятой спецификации языка Java.
Типы byte
, char
, int
long
, представляют собой числа с фиксированной запятой, которые являются точными представлениями чисел. В отличие от чисел с фиксированной запятой, числа с плавающей запятой иногда (можно с уверенностью предположить, что "большую часть времени") не смогут возвращать точное представление числа. Это причина, по которой вы в конечном итоге получаете 11.399999999999
в результате 5.6 + 5.8
.
Если требуется точное значение, например 1.5 или 150.1005, вы захотите использовать один из типов с фиксированной запятой, который сможет точно представлять число.
Как уже упоминалось несколько раз, в Java есть BigDecimal
класс, который будет обрабатывать очень большие и очень маленькие числа.
Из ссылки на Java API для BigDecimal
класса:
Неизменяемые десятичные числа со знаком произвольной точности. BigDecimal состоит из целого числа произвольной точности без масштабирования и 32-битного целочисленного масштаба. Если ноль или положительное значение, шкала представляет собой количество цифр справа от десятичной запятой. Если отрицательное, немасштабированное значение числа умножается на десять в степени отрицания шкалы. Следовательно, значение числа, представленного BigDecimal, равно (unscaledValue × 10^-scale).
По Stack Overflow было много вопросов, касающихся чисел с плавающей запятой и их точности. Вот список связанных вопросов, которые могут представлять интерес.:
- Почему я вижу переменную double, инициализированную некоторым значением, например 21.4, как 21.399999618530273?
- Как печатать действительно большие числа в C ++
- Как хранится плавающая точка? Когда это имеет значение?
- Использовать float или decimal для учета суммы в долларах приложения?
Если вы действительно хотите разобраться в мельчайших деталях чисел с плавающей запятой, взгляните на что каждый специалист по информатике должен знать об арифметике с плавающей запятой.
Ответ 2
Например, когда вы вводите двойное число, 33.33333333333333
получаемое значение на самом деле является ближайшим представимым значением двойной точности, которое точно:
33.3333333333333285963817615993320941925048828125
Деление этого значения на 100 дает:
0.333333333333333285963817615993320941925048828125
которое также не может быть представлено в виде числа с двойной точностью, поэтому оно снова округляется до ближайшего представимого значения, которое точно:
0.3333333333333332593184650249895639717578887939453125
Когда вы выводите это значение, оно еще раз округляется до 17 десятичных знаков, что дает:
0.33333333333333326
Ответ 3
Если вы просто хотите обрабатывать значения в виде дробей, вы можете создать класс Fraction, который содержит поля числителя и знаменателя.
Напишите методы для сложения, вычитания, умножения и деления, а также метод ToDouble. Таким образом, вы сможете избежать чисел с плавающей запятой во время вычислений.
РЕДАКТИРОВАТЬ: Быстрая реализация,
public class Fraction {
private int numerator;
private int denominator;
public Fraction(int n, int d){
numerator = n;
denominator = d;
}
public double toDouble(){
return ((double)numerator)/((double)denominator);
}
public static Fraction add(Fraction a, Fraction b){
if(a.denominator != b.denominator){
double aTop = b.denominator * a.numerator;
double bTop = a.denominator * b.numerator;
return new Fraction(aTop + bTop, a.denominator * b.denominator);
}
else{
return new Fraction(a.numerator + b.numerator, a.denominator);
}
}
public static Fraction divide(Fraction a, Fraction b){
return new Fraction(a.numerator * b.denominator, a.denominator * b.numerator);
}
public static Fraction multiply(Fraction a, Fraction b){
return new Fraction(a.numerator * b.numerator, a.denominator * b.denominator);
}
public static Fraction subtract(Fraction a, Fraction b){
if(a.denominator != b.denominator){
double aTop = b.denominator * a.numerator;
double bTop = a.denominator * b.numerator;
return new Fraction(aTop-bTop, a.denominator*b.denominator);
}
else{
return new Fraction(a.numerator - b.numerator, a.denominator);
}
}
}
Ответ 4
Обратите внимание, что у вас была бы та же проблема, если бы вы использовали десятичную арифметику ограниченной точности и хотели иметь дело с 1/3: 0.3333333333 * 3 равно 0.999999999, а не 1.00000000.
К сожалению, 5.6, 5.8 и 11.4 просто не являются круглыми числами в двоичном формате, потому что они включают пятые. Таким образом, их представление с плавающей запятой не является точным, так же как 0.3333 - это не совсем 1/3.
Если все используемые вами числа являются неповторяющимися десятичными дробями, и вы хотите получить точные результаты, используйте BigDecimal . Или, как говорили другие, если ваши значения подобны деньгам в том смысле, что все они кратны 0.01, или 0.001, или чему-то еще, тогда умножьте все на фиксированную степень 10 и используйте int или long (сложение и вычитание тривиальны: следите за умножением).
Однако, если вас устраивает двоичный формат для вычислений, но вы просто хотите распечатать данные в немного более удобном формате, попробуйте java.util.Formatter
или String.format
. В строке формата укажите точность, меньшую, чем полная точность double. Скажем, до 10 значащих цифр 11.399999999999 равно 11.4, поэтому результат будет почти таким же точным и более удобочитаемым для человека в случаях, когда двоичный результат очень близок к значению, требующему всего нескольких знаков после запятой.
Указываемая точность немного зависит от того, сколько математических операций вы проделали со своими числами - в общем, чем больше вы делаете, тем больше ошибок будет накапливаться, но некоторые алгоритмы накапливают ее намного быстрее, чем другие (они называются "нестабильными" в отличие от "стабильных" в отношении ошибок округления). Если все, что вы делаете, это добавляете несколько значений, то я бы предположил, что уменьшение точности всего на один десятичный знак все уладит. Поэкспериментируйте.