Сохранять точность с двойным в 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?

+132
источник поделиться
21 ответ

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

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

Примитивные типы float и double в Java - это номера с плавающей запятой, где номер хранится как двоичное представление фракции и экспоненты.

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

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

Эти части объединяются для создания представления double значения.

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

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

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

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

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

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

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

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

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

+138
источник

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

33.3333333333333285963817615993320941925048828125

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

0.333333333333333285963817615993320941925048828125

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

0.3333333333333332593184650249895639717578887939453125

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

0.33333333333333326
+101
источник
другие ответы

Связанные вопросы


Похожие вопросы

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

Напишите методы для добавления, вычитания, умножения и деления, а также метода 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);
    }
}

}
+23
источник

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

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

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

+15
источник

Вы можете изучить использование 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
+9
источник

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

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

+5
источник

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

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

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

Вы получите:

11.40
+5
источник

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

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

Решение зависит от вашей проблемы:

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

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

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

+5
источник
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
}
+4
источник

Использовать java.math.BigDecimal

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

+3
источник

Умножьте все на 100 и сохраните его задолго, как цента.

+2
источник

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

+2
источник

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

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

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

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

+2
источник

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

Java, создающая самое близкое двоичное приближение. Вы можете использовать java.text.DecimalFormat для отображения более десятичного десятичного значения.

+1
источник

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

+1
источник

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

Новый вызов будет выглядеть следующим образом:

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

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

0
источник

Почему бы не использовать метод 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
0
источник

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

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;
}
0
источник

Краткий ответ: всегда используйте BigDecimal и убедитесь, что вы используете конструктор с аргументом String, а не с двойным.

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

public class doublePrecision {
    public static void main(String[] args) {
      double total = new BigDecimal("0");
      total = total.add(new BigDecimal("5.6");
      total = total.add(new BigDecimal("5.8");
      System.out.println(total);
    }
}
0
источник

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

Для получения хорошего результата используйте:

System.out.printf("%.2f\n", total);
-1
источник

Посмотрите другие вопросы по меткам или Задайте вопрос