为什么Firebird在除法时会截断小数位?

问题描述 投票:0回答:1

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个回答
13
投票

在 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 标准指定。标准规定了所有这些操作的精度,以及除法的小数位数[sic],实现定义:我们将精度定义为 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>...

语法规则:

  1. 没有
    <exact numeric literal>
    <period>
    在最后一个
    <period>
    之后有一个隐含的
    <digit>

    22)
    <exact numeric literal>
    ENL 的声明类型是实现定义的精确数字类型,其小数位数是
    <digit>
    右侧的
    <period>
    的数量。应该有一个精确的数字类型能够准确地表示 ENL 的值。

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

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

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

解决方法是使用近似数字文字。使用指数零或

E0
将使数字成为双精度,而无需额外的 10 次幂。例如:

select 187E0/60.00 from rdb$database; -- result: 3.116666666666667
-- or
select 187/60.00E0 from rdb$database; -- result: 3.116666666666667
© www.soinside.com 2019 - 2024. All rights reserved.