Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Firebird truncate decimal places when dividing?

Tags:

sql

firebird

Firebird truncates decimal places when dividing, rather than rounding. Furthermore, it bases the number of decimal points in the returned value on the number of decimal places in the numerator and denominator.

Why is Firebird truncating instead of rounding? And why does it base the returned value on the number of decimal places in the query?

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
like image 292
Ray Zane Avatar asked Sep 30 '14 19:09

Ray Zane


1 Answers

In Firebird literals with a decimal point are of type NUMERIC, not DOUBLE PRECISION (or another floating point type). This means it will apply its exact numeric calculation rules.

So with select 187/60.00 from rdb$database this means that 187 is an INTEGER and 60.00 is a NUMERIC(18,2).

The rules for exact numeric calculation can be found in "Exact Numerics - Functional Specification":

If two operands OP1 and OP2 are exact numeric with scale S1 and S2 respectively, then OP1+OP2 and OP1-OP2 are exact numeric with precision 18 and scale the larger of S1 and S2, while OP1*OP2 and OP1/OP2 are exact numeric with precision 18 and scale S1+S2. (The scales of these operation except division are specified by the SQL standard. The standard makes the precision of all these operations, and the scale of divison, implementation-defined: we define the precision as 18, and the scale of division as S1+S2, the same as is required by the standard in the case of multiplication.)

When one of the operands is an integral type, it is considered as a numeric with scale 0. So in this case you have NUMERIC(18,0)/NUMERIC(18,2) and based on the above rules, the result is NUMERIC(18, 0+2) = NUMERIC(18,2).

The fact that the number appears to be truncated is a result of the application of exact numeric calculation: the calculation stops once the last digit has been calculated. The fact that there is a remainder has no bearing on the result of the calculation:

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

Looking at the SQL:2011 Foundation specification the fact Firebird considers 60.00 to be a exact numeric is correct, as it has the following production rules for literals in section 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>...

And syntax rules:

21) An <exact numeric literal> without a <period> has an implied <period> following the last <digit>.
22) The declared type of an <exact numeric literal> ENL is an implementation-defined exact numeric type whose scale is the number of <digit>s to the right of the <period>. There shall be an exact numeric type capable of representing the value of ENL exactly.

Section 6.27 <numeric value expression> specifies the following syntax rules:

1) If the declared type of both operands of a dyadic arithmetic operator is exact numeric, then the declared type of the result is an implementation-defined exact numeric type, with precision and scale determined as follows:
a) Let S1 and S2 be the scale of the first and second operands respectively.
b) The precision of the result of addition and subtraction is implementation-defined, and the scale is the maximum of S1 and S2.
c) The precision of the result of multiplication is implementation-defined, and the scale is S1 + S2.
d) The precision and scale of the result of division are implementation-defined.

In other words the behavior of Firebird complies with the SQL standard. By the looks of it most of the other database you tried (with the possible exception of SQL Server), either use a relatively large value for the scale when performing division, or seem to use the approximate numeric (aka double precision) behavior.

A workaround would be to use an approximate numeric literal. Use of exponent zero or E0 will make the number a double precision without additional powers of ten. For example:

select 187E0/60.00 from rdb$database; -- result: 3.116666666666667
-- or
select 187/60.00E0 from rdb$database; -- result: 3.116666666666667
like image 187
Mark Rotteveel Avatar answered Dec 02 '22 21:12

Mark Rotteveel