Вопрос-ответ

Retain precision with double in Java

Сохранение точности с помощью 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 было много вопросов, касающихся чисел с плавающей запятой и их точности. Вот список связанных вопросов, которые могут представлять интерес.:

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

Ответ 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, поэтому результат будет почти таким же точным и более удобочитаемым для человека в случаях, когда двоичный результат очень близок к значению, требующему всего нескольких знаков после запятой.

Указываемая точность немного зависит от того, сколько математических операций вы проделали со своими числами - в общем, чем больше вы делаете, тем больше ошибок будет накапливаться, но некоторые алгоритмы накапливают ее намного быстрее, чем другие (они называются "нестабильными" в отличие от "стабильных" в отношении ошибок округления). Если все, что вы делаете, это добавляете несколько значений, то я бы предположил, что уменьшение точности всего на один десятичный знак все уладит. Поэкспериментируйте.

java