Java. Точность вычислений.

Dec 30, 2009 16:57




Довольно часто при написании программ приходится выполнять операции над числами с плавающей точкой. Это и обыкновенные бухгалтерские программы, и сложные математические расчеты. Для преставления таких чисел в java служат два примитивных типа данных - float и double, а также их классы-обертки Float и Double соответственно. Для выполнения точных вычислений предназначен класс java.math.BigDecimal, и далее будет показано, что именно его нужно использовать для точных вычислений.

Рассмотрим хрестоматийный пример: сколько будет 2 / 3? Очевидно, периодическая дробь - 0,(6). В различных расчетах она обычно округляется до необходимой точности, например, в случае точности до третьего знака можно записать: 2 / 3 = 0,667. В случае точности до двадцатого знака: 2 / 3 = 0,66666666666666666667. Посмотрим теперь, какой ответ нам выдаст программа, написанная на java, будем рассматривать случаи использования различных типов данных, последовательно float, double и BigDecimal. Интересующая нас точность - до двадцатого знака.

Используем float.

public class Calculate
{
public static void main(String [] arg)
{
/*
* Указанием для компилятора, что мы используем число типа float является буква "f",
* поставленная после числа. Иначе в случае записи "2" компилятор рассматривал бы
* его как число типа int, а в случае записи "2.0" или "2d" - как число типа double.
*/
System.out.printf("%1.20f%n", 2f/3f);
}
}

После запуска этой программы на экран будет выдано 0,66666668653488160000. Т.е. до 7-го знака правильно, а дальше - ерунда. Связано это с особенностями представления числа с плавающей точкой в двоичном виде. Хотя float и может хранить числа с точностью до 2^(-149) степени, при выполнении арифметический операций точность понижается.

Используем double.

public class Calculate
{
public static void main(String [] arg)
{
System.out.printf("%1.20f%n", 2d/3d);
}
}

На экран будет выведено 0,66666666666666660000. Уже лучше, но округление какое-то странное, да и последние четыре разряда почему-то нулями заполнены. Т.е. делаем вывод - для точных математических вычислений ни float, ни double не годятся. Теперь самое интересное:

Используем BigDecimal.

Использовать данный класс несколько сложнее. Арифметические операции для него не определены, и для их выполнения придется использовать специальные функции - add, multiply, divide, substract и другие. Зато можно указать абсолютно любую требуемую точность - хоть до десятитысячного знака после запятой. Для указания точности и способа округления служит класс java.math.MathContext.

import java.math.*;

public class Calculate
{
public static void main(String [] arg)
{
MathContext mc = new MathContext(20);
BigDecimal a = new BigDecimal(2, mc);
BigDecimal b = new BigDecimal(3, mc);
System.out.println(a.divide(b, mc));
}
}

В результате выполнения этой программы наконец-то получим желаемые 0,66666666666666666667.

Добавлю, что разработчики java советуют использовать тип BigDecimal везде, где требуются точные вычисления. Программисты-практики знают, что в различных финансовых программах иногда приходится гоняться за пропавшей копейкой. Как это происходит?

Требуемая точность отображения - два знака после запятой. Скажем, нужно сложить два числа, но в результате вычислений одно из чисел равно 2,204 (отобразится по правилам округления как 2,20) а другое - 3,533 (отобразится как 5,53). С одной стороны, пользователь видит, что складываем 2,20 + 5,53 = 7,73. С другой стороны, программа складывает 2,204 + 5,533 = 7,737, по правилам округления - 7,74! Использование BigDecimal позволит такой ситуации избежать. Вот пример программы:

import java.math.*;

public class test
{
public static void main(String [] arg)
{
// Будет выведено: 2,20 + 3,53 = 7,74 - неверно!
System.out.printf("2,20 + 3,53 = %1.2f%n", 2.204f + 3.533);

MathContext mc = new MathContext(3);
BigDecimal a = new BigDecimal(2.204, mc);
BigDecimal b = new BigDecimal(3.533, mc);

// Ожидаемый результат: 2,20 + 3,53 = 7,73 - верно!
System.out.printf("2,20 + 5,53 = %s%n", a.add(b));
}
}

компьютеры, java

Previous post Next post
Up