小编典典

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

sql

Firebird在除法而不是舍入时会截断小数位。此外,它以分子和分母的小数位数为基础,将返回值的小数点位数作为基础。

为什么Firebird会被截断而不是四舍五入?为何将返回值基于查询中的小数位数?

Firebird2.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

阅读 214

收藏
2021-04-17

共1个答案

小编典典

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

因此,select 187/60.00 from rdb$database这意味着187是an,INTEGER而60.00是a
NUMERIC(18,2)

精确数字计算的规则可以在“精确数字-功能说明”中找到

如果两个操作数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)<exact numeric literal>没有a的<period>an表示<period>在last之后<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将使数字成为双精度,而没有额外的10的幂。例如:

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