Одним из наиболее сложных аспектов разработки программного обеспечения может быть работа с числами с плавающей запятой. Статья Дэвида Голдберга о компьютерных исследованиях 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
Сравнение Math.PI
с numeric
Math.PI
PostgreSQL
Простые примеры использования Java, Oracle и PostgreSQL демонстрируют проблемы, которые могут возникнуть при указании точности и масштаба для типов numeric
столбцов Oracle NUMBER
и PostgreSQL. Хотя существуют ситуации, когда числа с фиксированной запятой желательны, важно признать, что Java не имеет примитивного типа данных с фиксированной запятой, и использовать BigDecimal или библиотеку Java с фиксированной запятой (например, decimal4j или Java Math Fixed Point Library ) надлежащим образом работать с числами с фиксированной запятой, полученными из столбцов базы данных, выраженными в виде фиксированных В примерах, продемонстрированных в этом посте, нет ничего действительно «неправильного», но важно признать различие между числами с фиксированной запятой в базе данных и числами с плавающей запятой в Java, потому что арифметика, которая объединяет эти два значения, может не дать результатов. можно было бы ожидать.
В Java и других языках программирования нужно заботиться не только о влиянии арифметических операций и доступной точности на «правильность» чисел с плавающей точкой. Разработчик также должен знать, как эти числа хранятся в столбцах реляционной базы данных в базах данных Oracle и PostgreSQL, чтобы понять, как точность и масштабирование обозначений в этих столбцах могут влиять на представление сохраненного числа с плавающей запятой. Это особенно применимо, если представления, запрашиваемые из базы данных, должны использоваться в вычислениях с плавающей запятой. Это еще один (из многих) примеров, когда для разработчика Java важно понимать используемую схему базы данных.
Ссылка: | Фиксированная точка и с плавающей точкой: две вещи, которые не сочетаются друг с другом от нашего партнера JCG Дастина Маркса в блоге Inspired by Actual Events . |