Сохранять точность с двойным в 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 - это fixed-point, которые являются точными представлениями номера. В отличие от чисел с фиксированной точкой числа с плавающей запятой будут несколько раз (безопасно предполагать "большую часть времени" ) не смогут вернуть точное представление числа. Именно по этой причине вы оказываетесь 11.399999999999 в результате 5.6 + 5.8.

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

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

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

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

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

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

Ответ 2

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

33.3333333333333285963817615993320941925048828125

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

0.333333333333333285963817615993320941925048828125

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

0.3333333333333332593184650249895639717578887939453125

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

0.33333333333333326

Ответ 3

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

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

}

Ответ 4

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

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

Ответ 5

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

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

import java.math.BigDecimal;
/**
 * Created by a wonderful programmer known as:
 * Vincent Stoessel
 * [email protected]
 * 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

Ответ 6

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

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

Ответ 7

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

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

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

Вы получите:

11.40

Ответ 8

Вы не можете, потому что 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.

Ответ 9

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

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

Ответ 10

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
}

Ответ 11

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

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

Ответ 12

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

Ответ 13

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

Ответ 14

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

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

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

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

Ответ 15

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

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

Ответ 16

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

Ответ 17

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

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

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

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

Ответ 18

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

Ответ 19

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

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

Ответ 20

Краткий ответ: всегда используйте 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);
    }
}

Ответ 21

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

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

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