为什么Firebird在分割时会截断小数位?

Ray*_*ane 13 sql 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
Run Code Online (Sandbox Code Playgroud)

SQL Server 2012:

select 187/60.00; --result: 3.116666
Run Code Online (Sandbox Code Playgroud)

Oracle 11gR2:

select 187/60.00 from dual; --result: 3.116666666667
Run Code Online (Sandbox Code Playgroud)

MySQL 5.5.32:

select 187/60.00 from dual; --result: 3.1167
Run Code Online (Sandbox Code Playgroud)

PostgreSQL 9.3.1:

select 187/60.00; --result: 3.116666666667
Run Code Online (Sandbox Code Playgroud)

SQLite的:

select 187/60.00; --result: 3.1166666666666667
Run Code Online (Sandbox Code Playgroud)

Mar*_*eel 13

在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 
Run Code Online (Sandbox Code Playgroud)

查看SQL:2011 Foundation规范,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>...
Run Code Online (Sandbox Code Playgroud)

和语法规则:

21)<exact numeric literal>没有一个<period>隐含<period>在最后一个之后<digit>.
22)<exact numeric literal>ENL 的声明类型是一个实现定义的精确数字类型,其标度是<digit>在右边的s 的数量<period>.应该有一个精确的数字类型,能够准确地表示ENL的值.

6.27节<数值表达式>指定以下语法规则:

1)如果二元算术运算符的两个操作数的声明类型是精确数字,则声明的结果类型是实现定义的精确数字类型,精度和比例确定如下:
a)设S1和S2为分别是第一和第二操作数的比例.
b)加法和减法结果的精度是实现定义的,并且比例是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
Run Code Online (Sandbox Code Playgroud)