【问题标题】:Why does Firebird truncate decimal places when dividing?为什么Firebird在除法时会截断小数位?
【发布时间】:2014-11-25 12:36:48
【问题描述】:

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

【问题讨论】:

    标签: sql firebird


    【解决方案1】:

    在 Firebird 中,带小数点的文字是 NUMERIC 类型,而不是 DOUBLE PRECISION(或其他浮点类型)。这意味着它将应用其精确的数字计算规则。

    所以select 187/60.00 from rdb$database 这意味着 187 是 INTEGER 而 60.00 是 NUMERIC(18,2)

    精确数值计算规则见"Exact Numerics - Functional Specification"

    如果两个操作数 OP1 和 OP2 分别是精度为 S1 和 S2 的精确数字,则 OP1+OP2 和 OP1-OP2 是精度为 18 的精确数字,并缩放 S1 和 S2 中的较大者,而 OP1*OP2 和 OP1/OP2是精确的数字,精度为 18,比例为 S1+S2。 (除除法之外的这些运算的尺度是由SQL标准规定的。标准使所有这些运算的精度,以及除法的尺度,实现定义:我们定义精度为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 
    

    查看 SQL:2011 Foundation 规范,Firebird 认为 60.00 是一个精确数字的事实是正确的,因为它在第 5.3 节 中具有以下文字生成规则:

    <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) 没有&lt;period&gt;&lt;exact numeric literal&gt; 在最后一个&lt;digit&gt; 之后有一个隐含的&lt;period&gt;
    22) &lt;exact numeric literal&gt; ENL 的声明类型是实现定义的精确数字类型,其标度为&lt;period&gt; 右侧的&lt;digit&gt;s 的数量。应该有一个精确的数值类型能够准确地表示 ENL 的值。

    第 6.27 节 指定以下语法规则:

    1) 如果二元算术运算符的两个操作数的声明类型都是精确数字,则结果的声明类型是实现定义的精确数字类型,其精度和小数位数确定如下:
    a) 令 S1 和 S2 分别为第一个和第二个操作数的标度。
    b) 加减运算结果的精度由实现定义,尺度为S1和S2中的最大值。
    c) 乘法结果的精度由实现定义,尺度为 S1 + S2。
    d) 除法结果的精度和小数位数由实现定义。

    换句话说,Firebird 的行为符合 SQL 标准。从外观上看,您尝试过的大多数其他数据库(SQL Server 可能除外),要么在执行除法时使用相对较大的比例值,要么似乎使用近似数字(又名双精度)行为。

    一种解决方法是使用近似数字文字。使用指数 0 或 E0 将使数字成为双精度,而无需额外的 10 次方。例如:

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

    【讨论】:

      猜你喜欢
      • 2018-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-29
      相关资源
      最近更新 更多