Сохраняйте точность с двойным в Java - PullRequest
130 голосов
/ 27 ноября 2008
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?

Ответы [ 20 ]

130 голосов
/ 27 ноября 2008

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

Из справочника по API Java для класса BigDecimal:

Неизменное, знаковая десятичная дробь произвольной точности номера. BigDecimal состоит из произвольная точность целое число немасштабированное значение и 32-битная целочисленная шкала. Если ноль или положительный, шкала количество цифр справа от десятичная точка. Если отрицательный, то немасштабированное значение числа умножить на десять до степени отрицание масштаба. Значение число, представленное Поэтому BigDecimal (unscaledValue × 10 ^ (шкала).

Было много вопросов о переполнении стека, касающихся вопроса о числах с плавающей запятой и его точности. Вот список связанных вопросов, которые могут представлять интерес:

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

99 голосов
/ 18 марта 2010

Когда вы вводите двойное число, например, 33.33333333333333, полученное вами значение фактически является ближайшим представимым значением двойной точности, а именно:

33.3333333333333285963817615993320941925048828125

Деление на 100 дает:

0.333333333333333285963817615993320941925048828125

, который также не представляется в виде числа с двойной точностью, поэтому он снова округляется до ближайшего представимого значения, которое в точности равно:

0.3333333333333332593184650249895639717578887939453125

Когда вы выводите это значение, оно округляется еще раз до 17 десятичных цифр, давая:

0.33333333333333326
23 голосов
/ 18 марта 2010

Если вы просто хотите обрабатывать значения как дроби, вы можете создать класс 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);
    }
}

}
15 голосов
/ 27 ноября 2008

Обратите внимание, что у вас возникнет та же проблема, если вы используете десятичную арифметику с ограниченной точностью и хотите иметь дело с 1/3: 0,333333333 * 3 - это 0,9999999999, а не 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. В строке формата укажите точность, меньшую полной точности в два раза. Например, для 10 значащих цифр 11,399999999999 равно 11,4, поэтому результат будет почти таким же точным и более понятным для человека в тех случаях, когда двоичный результат очень близок к значению, требующему только несколько десятичных знаков.

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

9 голосов
/ 18 марта 2010

Возможно, вы захотите изучить использование java-класса java.math.BigDecimal, если вам действительно нужна точность математики. Вот хорошая статья от Oracle / Sun о случае BigDecimal . В то время как вы никогда не сможете представить 1/3, как кто-то упомянул, вы можете иметь право точно решить, насколько точным вы хотите получить результат. setScale () твой друг ..:)

Хорошо, потому что у меня слишком много времени на руках, вот пример кода, который относится к вашему вопросу:

import java.math.BigDecimal;
/**
 * Created by a wonderful programmer known as:
 * Vincent Stoessel
 * xaymaca@gmail.com
 * on Mar 17, 2010 at  11:05:16 PM
 */
public class BigUp {

    public static void main(String[] args) {
        BigDecimal first, second, result ;
        first = new BigDecimal("33.33333333333333")  ;
        second = new BigDecimal("100") ;
        result = first.divide(second);
        System.out.println("result is " + result);
       //will print : result is 0.3333333333333333


    }
}

и, в дополнение к моему новому любимому языку, Groovy, вот еще один пример того же:

import java.math.BigDecimal

def  first =   new BigDecimal("33.33333333333333")
def second = new BigDecimal("100")


println "result is " + first/second   // will print: result is 0.33333333333333
5 голосов
/ 27 ноября 2008

Уверен, вы могли бы превратить это в пример из трех строк. :)

Если вам нужна точная точность, используйте BigDecimal. В противном случае вы можете использовать целые числа, умноженные на 10 ^, с любой точностью, которую вы хотите.

5 голосов
/ 18 марта 2010

Вы не можете, потому что 7.3 не имеет конечного представления в двоичном формате. Ближайшее, что вы можете получить: 2054767329987789/2 ** 48 = 7,3 + 1 / 1407374883553280.

Взгляните на http://docs.python.org/tutorial/floatingpoint.html для дальнейшего объяснения. (Он находится на веб-сайте Python, но Java и C ++ имеют одну и ту же «проблему».)

Решение зависит от того, что именно является вашей проблемой:

  • Если вам просто не нравится видеть все эти цифры шума, исправьте форматирование строки. Не отображайте более 15 значащих цифр (или 7 для числа с плавающей запятой).
  • Если неточность ваших чисел нарушает такие вещи, как операторы "если", то вам следует написать if (abs (x - 7.3)
  • Если вы работаете с деньгами, то, что вы, вероятно, действительно хотите, это десятичная фиксированная точка. Храните целое число центов или любую меньшую единицу вашей валюты.
  • (ОЧЕНЬ НЕВЕРНО) Если вам нужно более 53 значащих бит (15-16 значащих цифр) точности, используйте высокоточный тип с плавающей запятой, такой как BigDecimal.
5 голосов
/ 27 ноября 2008

Как уже отмечали другие, не все десятичные значения могут быть представлены в двоичном виде, поскольку десятичная дробь основана на степенях 10, а двоичная - на степенях двух.

Если точность имеет значение, используйте BigDecimal, но если вы просто хотите дружественный вывод:

System.out.printf("%.2f\n", total);

Даст вам:

11.40
5 голосов
/ 18 марта 2010

Вы столкнулись с ограничением точности типа double.

Java.Math имеет некоторые арифметические средства произвольной точности.

4 голосов
/ 07 декабря 2011
private void getRound() {
    // this is very simple and interesting 
    double a = 5, b = 3, c;
    c = a / b;
    System.out.println(" round  val is " + c);

    //  round  val is  :  1.6666666666666667
    // if you want to only two precision point with double we 
            //  can use formate option in String 
           // which takes 2 parameters one is formte specifier which 
           // shows dicimal places another double value 
    String s = String.format("%.2f", c);
    double val = Double.parseDouble(s);
    System.out.println(" val is :" + val);
    // now out put will be : val is :1.67
}
...