Сохранить точность с помощью 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?

20 ответов


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

теперь небольшое объяснение, почему это происходит:

на float и double примитивные типы в Java являются с плавающей точкой чисел, где число хранится в виде двоичного представления дроби и степени.

более конкретно, двойной точности с плавающей значение точки, такое как double type-64-разрядное значение, где:

  • 1 бит обозначает знак (положительный или отрицательный).
  • 11 бит для экспоненты.
  • 52 бита для значащих цифр (дробная часть как двоичная).

эти части совмещены для того чтобы произвести double представление значения.

(источник: Википедия: двойная точность)

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

на byte, char, int, long типы фиксированной точки числа, которые являются точными representions чисел. В отличие от чисел с фиксированной точкой, числа с плавающей запятой несколько раз (безопасно предположить "большую часть времени") не смогут вернуть точное представление числа. Это почему вы в конечном итоге с помощью 11.399999999999 в результате 5.6 + 5.8.

когда требуется точное значение, например 1.5 или 150.1005, вы захотите использовать один из типов с фиксированной точкой, который сможет точно представлять число.

как уже упоминалось несколько раз, в Java BigDecimal класс, который будет обрабатывать очень большие числа и очень маленькие числа.

из ссылки Java API для BigDecimal класс:

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

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

если вы действительно хотите, чтобы перейти к мелочи песчаной детали чисел с плавающей запятой, взгляните на Что Каждый Компьютерщик Должен Знать Об Арифметике С Плавающей Запятой.


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

33.3333333333333285963817615993320941925048828125

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

0.333333333333333285963817615993320941925048828125

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

0.3333333333333332593184650249895639717578887939453125

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

0.33333333333333326

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

методы записи для сложения, вычитания, умножения и деления, а также метод toDouble. Таким образом, вы можете избежать поплавков во время вычислений.

EDIT: быстрая реализация,

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);
    }
}

}

заметьте, что у вас была бы та же проблема, если бы вы использовали десятичную арифметику с ограниченной точностью и хотели иметь дело с 1/3: 0.333333333 * 3-0.999999999, а не 1.00000000.

к сожалению, 5.6, 5.8 и 11.4 просто не круглые числа в двоичном формате, потому что они включают пятые. Таким образом, флоат-представление их не является точным, так же как 0.3333 не является точно 1/3.

Если все числа, которые вы используете, являются непериодическими десятичными, и вы хотите получить точные результаты, используйте BigDecimal. Или как говорили другие, если ваши значения подобны деньгам в том смысле, что все они кратны 0,01, или 0,001, или чему-то еще, то умножьте все на фиксированную степень 10 и используйте int или long (сложение и вычитание тривиальны: следите за умножением).

однако, если вы довольны binary для расчета, но вы просто хотите распечатать вещи в немного более дружелюбном формате, попробуйте java.util.Formatter или String.format. В строке формата укажите точность меньше полной точность двойника. К 10 значимым цифрам, скажем, 11.39999999999999 - 11.4, поэтому результат будет почти таким же точным и более читаемым человеком в случаях, когда двоичный результат очень близок к значению, требующему всего нескольких десятичных знаков.

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


вы можете изучить использование java java.математика.Класс BigDecimal, если вам действительно нужна точная математика. Вот хорошая статья от Oracle / Sun on дело для BigDecimal. Хотя вы никогда не можете представить 1/3, как кто-то упомянул, вы can имеют право решать, насколько точно вы хотите, чтобы результат был. 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

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

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

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

даст вам:

11.40

уверен, что вы могли бы сделать это в трехстрочном примере. :)

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


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

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


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

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

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

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

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
}

используйте java.математика.У bigdecimal

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


умножьте все на 100 и сохраните его в виде центов.


компьютеры хранят числа в двоичном формате и не могут фактически представлять числа, такие как 33.333333333 или 100.0 точно. Это одна из хитростей в использовании двойников. Вам нужно будет просто округлить ответ, прежде чем показывать его пользователю. К счастью, в большинстве приложений вам не нужно так много десятичных знаков.


числа с плавающей запятой отличаются от действительных чисел тем, что для любого заданного числа с плавающей запятой существует следующее более высокое число с плавающей запятой. То же, что и целые числа. Между 1 и 2 нет целого числа.

невозможно представить 1/3 в виде поплавка. Под ним поплавок, над ним поплавок, и между ними определенное расстояние. А 1/3 находится в этом пространстве.

Apfloat для Java утверждает, что работает с произвольными точными числами с плавающей запятой, но у меня есть никогда им не пользовался. Наверное, стоит взглянуть. http://www.apfloat.org/apfloat_java/

аналогичный вопрос был задан здесь раньше библиотека высокой точности с плавающей запятой Java


парный аппроксимаций десятичных чисел в вашем источнике Java. Вы видите следствие несоответствия между double (который является двоичным значением) и вашим источником (который является десятичным кодом).

Java производит ближайшее двоичное приближение. Вы можете использовать Java.текст.DecimalFormat, чтобы отобразить более красивое десятичное значение.


используйте BigDecimal. Он даже позволяет указать правила округления (например, ROUND_HALF_EVEN, который минимизирует статистическую ошибку путем округления до четного соседа, если оба равны расстоянию; т. е. как 1,5, так и 2,5 раунда до 2).


Проверьте BigDecimal, он обрабатывает проблемы, связанные с арифметикой с плавающей запятой.

новый вызов будет выглядеть так:

term[number].coefficient.add(co);

используйте setScale (), чтобы установить количество точности десятичного знака.


почему бы не использовать метод round() из класса Math?

// The number of 0s determines how many digits you want after the floating point
// (here one digit)
total = (double)Math.round(total * 10) / 10;
System.out.println(total); // prints 11.4

Если у вас нет выбора, кроме как использовать двойные значения, можно использовать приведенный ниже код.

public static double sumDouble(double value1, double value2) {
    double sum = 0.0;
    String value1Str = Double.toString(value1);
    int decimalIndex = value1Str.indexOf(".");
    int value1Precision = 0;
    if (decimalIndex != -1) {
        value1Precision = (value1Str.length() - 1) - decimalIndex;
    }

    String value2Str = Double.toString(value2);
    decimalIndex = value2Str.indexOf(".");
    int value2Precision = 0;
    if (decimalIndex != -1) {
        value2Precision = (value2Str.length() - 1) - decimalIndex;
    }

    int maxPrecision = value1Precision > value2Precision ? value1Precision : value2Precision;
    sum = value1 + value2;
    String s = String.format("%." + maxPrecision + "f", sum);
    sum = Double.parseDouble(s);
    return sum;
}

Не тратьте свои усилия, используя BigDecimal. В 99,99999% случаев он вам не нужен. java двойной тип, конечно, приблизительный, но почти во всех случаях он достаточно точен. Имейте в виду, что у вас есть ошибка в 14-й значащей цифре. Это действительно ничтожно мало!

чтобы получить хороший выход:

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