Статьи

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

Одним из наиболее сложных аспектов разработки программного обеспечения может быть работа с числами с плавающей запятой. Статья Дэвида Голдберга о компьютерных исследованиях 1991 года, которую должен знать каждый компьютерщик об арифметике с плавающей точкой, является признанным классическим трактатом на эту тему . Эта статья не только дает углубленный взгляд на то, как арифметика с плавающей запятой реализуется в большинстве языков программирования и компьютерных систем, но также, благодаря своей длине и деталям, свидетельствует о нюансах и трудностях этого предмета. Нюансы работы с числами с плавающей точкой в ​​Java и тактики для преодоления этих проблем хорошо документированы в таких источниках, как арифметика JavaWorld с плавающей точкой , новая математика Java для IBM DeveloperWorks , Часть 2. Числа с плавающей точкой и теория Java и практика: где ваша точка зрения? , Прецизионные и фиксированные, плавающие и точные вычисления доктора Добба с Java с помощью Bigdecimal , плавающая точка Java-глоссария, примитивные типы данных Java Tutorial и NUM04-J. Не используйте числа с плавающей точкой, если требуется точное вычисление .

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

База данных Oracle позволяет выражать числовые столбцы типа данных NUMBER двумя целыми числами, которые представляют «точность» и «масштаб». Реализация PostgreSQL числового типа данных очень похожа. Oracle NUMBER(p,s) и PostgreSQL numeric(p,s) позволяют одному и тому же типу данных по существу представлять целое значение (точность указана, но масштаб не указан), число с фиксированной точкой (указана точность и масштаб) или плавающее значение номер точки (не указана ни точность, ни масштаб). Простые примеры на основе Java / JDBC в этом посте продемонстрируют это.

Для примеров в этом посте будет создана простая таблица с именами DOUBLES в Oracle и doubles в PostgreSQL. Операторы DDL для определения этих простых таблиц в двух базах данных показаны далее.

createOracleTable.sql

1
2
3
4
5
6
CREATE TABLE doubles
(
   int NUMBER(5),
   fixed NUMBER(3,2),
   floating NUMBER
);

createPgTable.sql

1
2
3
4
5
6
CREATE TABLE doubles
(
   int numeric(5),
   fixed numeric(3,2),
   floating numeric
);

С таблицей DOUBLES созданной в базе данных Oracle и базе данных PostgreSQL, я затем буду использовать простой JDBC PreparedStatement, чтобы вставить значение java.lang.Math.PI в каждую таблицу для всех трех столбцов. Следующий фрагмент кода Java демонстрирует эту вставку.

Вставка Math.PI в столбцы DOUBLES

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
/** SQL syntax for insertion statement with placeholders. */
private static final String INSERT_STRING =
   "INSERT INTO doubles (int, floating, fixed) VALUES (?, ?, ?)";
 
 
final Connection connection = getDatabaseConnection(databaseVendor);
try (final PreparedStatement insert = connection.prepareStatement(INSERT_STRING))
{
   insert.setDouble(1, Math.PI);
   insert.setDouble(2, Math.PI);
   insert.setDouble(3, Math.PI);
   insert.execute();
}
catch (SQLException sqlEx)
{
   err.println("Unable to insert data - " + sqlEx);
}

Запрос столбцов DOUBLES

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/** SQL syntax for querying statement. */
private static final String QUERY_STRING =
   "SELECT int, fixed, floating FROM doubles";
 
final Connection connection = getDatabaseConnection(databaseVendor);
try (final Statement query = connection.createStatement();
     final ResultSet rs = query.executeQuery(QUERY_STRING))
{
   out.println("\n\nResults for Database " + databaseVendor + ":\n");
   out.println("Math.PI :        " + Math.PI);
   while (rs.next())
   {
      final double integer = rs.getDouble(1);
      final double fixed = rs.getDouble(2);
      final double floating = rs.getDouble(3);
      out.println("Integer NUMBER:  " + integer);
      out.println("Fixed NUMBER:    " + fixed);
      out.println("Floating NUMBER: " + floating);
   }
   out.println("\n");
}
catch (SQLException sqlEx)
{
   err.println("Unable to query data - " + sqlEx);
}

Результат выполнения вышеуказанной вставки Java и запроса кода к базам данных Oracle и PostgreSQL, соответственно, показан на следующих двух снимках экрана.

Сравнение Math.PI с Math.PI Oracle NUMBER

20161103-оракул-двойники

Сравнение Math.PI с numeric Math.PI PostgreSQL

20161103-двойники для PostgreSQL

Простые примеры использования Java, Oracle и PostgreSQL демонстрируют проблемы, которые могут возникнуть при указании точности и масштаба для типов numeric столбцов Oracle NUMBER и PostgreSQL. Хотя существуют ситуации, когда числа с фиксированной запятой желательны, важно признать, что Java не имеет примитивного типа данных с фиксированной запятой, и использовать BigDecimal или библиотеку Java с фиксированной запятой (например, decimal4j или Java Math Fixed Point Library ) надлежащим образом работать с числами с фиксированной запятой, полученными из столбцов базы данных, выраженными в виде фиксированных В примерах, продемонстрированных в этом посте, нет ничего действительно «неправильного», но важно признать различие между числами с фиксированной запятой в базе данных и числами с плавающей запятой в Java, потому что арифметика, которая объединяет эти два значения, может не дать результатов. можно было бы ожидать.

В Java и других языках программирования нужно заботиться не только о влиянии арифметических операций и доступной точности на «правильность» чисел с плавающей точкой. Разработчик также должен знать, как эти числа хранятся в столбцах реляционной базы данных в базах данных Oracle и PostgreSQL, чтобы понять, как точность и масштабирование обозначений в этих столбцах могут влиять на представление сохраненного числа с плавающей запятой. Это особенно применимо, если представления, запрашиваемые из базы данных, должны использоваться в вычислениях с плавающей запятой. Это еще один (из многих) примеров, когда для разработчика Java важно понимать используемую схему базы данных.