Why does Firebird truncate decimal places when dividing?

前端 未结 1 594
暖寄归人
暖寄归人 2021-02-13 13:20

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 i

相关标签:
1条回答
  • 2021-02-13 14:03

    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
    
    0 讨论(0)
提交回复
热议问题