Подтвердить что ты не робот

Почему Firebird усекает десятичные разряды при делении?

Firebird усекает десятичные разряды при делении, а не округлении. Кроме того, он основывает число десятичных точек в возвращаемом значении на число десятичных знаков в числителе и знаменателе.

Почему Firebird усекает вместо округления? И почему он основывает возвращаемое значение на количестве десятичных знаков в запросе?

Firebird 2.5:

select 187/60.00 from rdb$database; --result: 3.11
select 187.000/60 from rdb$database; --result: 3.116
select 187.000/60.00 from rdb$database --result: 3.11666

SQL Server 2012:

select 187/60.00; --result: 3.116666

Oracle 11gR2:

select 187/60.00 from dual; --result: 3.116666666667

MySQL 5.5.32:

select 187/60.00 from dual; --result: 3.1167

PostgreSQL 9.3.1:

select 187/60.00; --result: 3.116666666667

SQLite:

select 187/60.00; --result: 3.1166666666666667
4b9b3361

Ответ 1

В литералах Firebird с десятичной точкой есть тип NUMERIC, а не DOUBLE PRECISION (или другой тип с плавающей точкой). Это означает, что он будет применять свои точные правила численного расчета.

Итак, с select 187/60.00 from rdb$database это означает, что 187 является INTEGER, а 60,00 - NUMERIC(18,2).

Правила точного численного расчета можно найти в "Точные числа - функциональная спецификация" :

Если два операнда OP1 и OP2 являются точными числами с шкалой S1 и S2 соответственно, тогда OP1 + OP2 и OP1-OP2 являются точными числами с точностью 18 и масштабируют больше из S1 и S2, тогда как OP1 * OP2 и OP1/OP2 являются точными числами с точностью 18 и шкалой S1 + S2. (Масштабы этой операции, кроме разделения, определяются стандартом SQL. Стандарт делает точность всех этих операций и шкалу divison, определяемую реализацией: мы определяем точность как 18, а масштаб деления как S1 + S2, то же, что требуется стандарту в случае умножения.)

Когда один из операндов является интегральным типом, он считается числовым с масштабом 0. Итак, в этом случае у вас есть NUMERIC(18,0)/NUMERIC(18,2) и на основе вышеприведенных правил результат NUMERIC(18, 0+2) = NUMERIC(18,2).

Тот факт, что число оказывается усеченным, является результатом применения точного численного расчета: расчет останавливается после вычисления последней цифры. Тот факт, что есть остаток, не влияет на результат расчета:

60.00 / 187 \ 3.11
        180
        ---
          70
          60
          --
          100
           60
           -- (stop)
           40 

Глядя на спецификацию Foundation SQL: 2011, факт, что Firebird считает, что 60.00 является точным числовым, является правильным, так как он имеет следующие правила производства для литералов в разделе 5.3 <literal> :

<literal> ::=
    <signed numeric literal>
  | <general literal>

<unsigned literal> ::=
    <unsigned numeric literal>
  | <general literal>

<signed numeric literal> ::=
    [ <sign> ] <unsigned numeric literal>

<unsigned numeric literal> ::=
    <exact numeric literal>
  | <approximate numeric literal>

<exact numeric literal> ::=
    <unsigned integer> [ <period> [ <unsigned integer> ] ]
  | <period> <unsigned integer>

<sign> ::=
    <plus sign>
  | <minus sign>

<approximate numeric literal> ::=
    <mantissa> E <exponent>

<mantissa> ::=
    <exact numeric literal>

<exponent> ::=
    <signed integer>

<signed integer> ::=
    [ <sign> ] <unsigned integer>

<unsigned integer> ::=
    <digit>...

И правила синтаксиса:

21) <exact numeric literal> без <period> имеет подразумеваемый <period>, следующий за последним <digit>.
22) Объявленный тип <exact numeric literal> ENL - это точный числовой тип, определенный реализацией, масштаб которого - это число <digit> справа от <period>. Должен быть точный числовой тип, способный точно представлять значение ENL.

Раздел 6.27 < числовое выражение выражения > задает следующие правила синтаксиса:

1) Если объявленный тип обоих операндов диадического арифметического оператора является точным числовым, то объявленный тип результата представляет собой точный числовой тип, определенный реализацией, с точностью и шкалой, определяемыми следующим образом: а) Пусть S1 и S2 - масштаб первого и второго операндов соответственно. б) Точность результата сложения и вычитания определяется реализацией, а масштаб является максимальным значением S1 и S2.
c) Точность результата умножения определяется реализацией, а масштаб - S1 + S2.
d) Точность и масштаб результата деления определяются реализацией.

Другими словами, поведение Firebird соответствует стандарту SQL. По его мнению, большая часть другой базы данных, которую вы пытались (за исключением, возможно, SQL Server), либо использовать относительно большое значение для шкалы при выполнении деления, либо, по-видимому, использовать приблизительное числовое поведение (ака двойной точности).

Обходным решением будет использование приблизительного числового литерала. Использование показателя экспоненты или E0 сделает число двойной точностью без дополнительных степеней десяти. Например:

select 187E0/60.00 from rdb$database; -- result: 3.116666666666667
-- or
select 187/60.00E0 from rdb$database; -- result: 3.116666666666667