Decimal Operators
DECIMAL 类型旨在精确表示浮点数。除法外,对十进制值的数学运算都是精确的。 而 DOUBLE 和 REAL 类型则旨在近似表示浮点数。对双精度值和实数的数学运算都是近似的。
例如 ,数字 5,000,000,000,000,000 可以用 DOUBLE 类型表示。 但是,数字 5,000,000,000,000,000.15 无法用 DOUBLE 类型表示,但可以用 DECIMAL 类型表示。 更多详细信息,请参阅 https://en.wikipedia.org/wiki/Double-precision_floating-point_format。
DECIMAL 类型有两个参数:精度和小数位数。精度是指用于表示数字的总位数。小数位数是指小数点后的位数。当然,小数位数不能超过精度。 此外,精度不能超过 38。
`
decimal(p, s)
p >= 1 && p <= 38
s >= 0 && s <= p
数字 5,000,000,000,000,000.15 可以用 DECIMAL(18, 2) 表示。此数字至少需要 18 位数字(精度),其中小数点后必须至 少有 2 位数字(小数位数)。此数字可以使用任何小数位数 >= 2 且精度 >= 小数位数 + 16 的 DECIMAL 类型表示。
注意:精度和小数位数的定义可能看起来违反直觉。通常,我们会将小数点后的位数视为精度,将小数点前的位数视为小数位数。
Addition and Subtraction
为了表示两个十进制数相加的结果,我们需要在小数点后使用 max(s1, s2) 位数字,在小数点前使用 max(p1 - s1, p2 - s2) + 1 位数字。
p = max(p1 - s1, p2 - s2) + 1 + max(s1, s2)
s = max(s1, s2)
理解这个公式最简单的方法是想象一下列加法,我们将两个数字依次排列,并对齐小数点。
1.001
9999.5
-----------
10000.501
我们可以看到,结果小数点后需要 max(s1, s2) 位数字,小数点前需要 max(p1 - s1, p2 - s2) + 1 位数字。
结果的精度可能超过 38 位。有两种方案。一种方案是,只要 p <= 38,就支持加法和减法,并拒绝 p > 38 的运算。另一种方案是,将 p
限制为 38,只要实际结果可以用 38 位数字表示,就允许运算成功。在这种情况下,当实际结果无法用 38 位数字表示时,用户会遇到运行
时错误。Presto 实现了第二种方案。Pollux 的实现与 Presto 一致。
Multiplication
为了表示两个十进制数相乘的结果,我们需要小数点后 s1 + s2 位数字,总共需要 p1 + p2 位数字。
p = p1 + p2
s = s1 + s2
要将两个数相乘,我们可以先将它们作为忽略小数点的整数相乘,然后将原始数的小数点后的位数相 加,并将结果中的小数点向后移几位。
要将 0.01 与 0.001 相乘,我们可以将 1 与 1 相乘,然后将小数点向左移 5 位:0.00001。因此,结果的小数位数是输入小数位数之和。
当将两个分别具有 p1 位和 p2 位数字的整数相乘时,我们得到的结果严格小于 10^p1 * 10^p2 = 10^(p1 + p2)。因此,我们最多需要 p1 + p2
位数字来表示结果。
结果的小数位数和精度都可能超过 38 位。同样,这里也有两种选择。一种方案是,只要 p <= 38,乘法就受支持(根据定义,如果 p <= 38,s 不超过 p,
因此不超过 38)。另一种方案是,将 p 和 s 的上限设为 38,只要实际结果可以表示为 decimal(38, s),运算就成功。在这种情况下,当实际结果不能表示为
decimal(38, s) 时,用户会遇到运行时错误。Presto 实现了第三种方案:如果 s 超过 38,则拒绝运算;如果 s <= 38,则将 p 的上限设为 38。在这
种情况下,某些运算会被直接拒绝,而其他运算则允许继续,但可能会产生运行时错误。Pollux 的实现与 Presto 一致。
Division
完美的除法是不可能的。例如,1 / 3 无法用十进制数表示。
当用 p1 位数字除以 s2 位数时,最大的结果需要在小数点前多加 s2 位数字。为了得到最大的数,我们必须除以 0.0000001,这实际上是乘以 10^s2。 因此,结果的精度至少需要达到 p1 + s2。
Presto 还会选择将结果的位数扩展到输入的最大位数。
s = max(s1, s2)
为了支持更大的规模,结果精度需要通过 s1 和 s 的差值进行扩展。
p = p1 + s2 + max(0, s2 - s1)
与加法类似,结果的精度可能超过 38 位。选择相同。Presto 选择将 p 的精度限制为 38 位,并允许出现运行时错误。
假设 a 的类型为 decimal(p1, s1),其未缩放值为 A;b 的类型为 decimal(p2, s2),其未缩放值为 B。
a = A / 10^s1
b = B / 10^s2
结果类型精度和小数位数为:
s = max(s1, s2)
p = p1 + s2 + max(0, s2 - s1)
结果 'r' 的小数点后有 's' 位数字,且为非缩放值 R。我们推导出 R 的值如下:
r = a / b = (A / 10^s1) / (B / 10^s2) = A * 10^(s2 - s1) / B
r = R / 10^s
R = r * 10^s = A * 10^(s + s2 - s1) / B
要计算 R,首先使用缩放因子 (s + s2 - s1) 缩放 A, 然后除以 B 并四舍五入为最接近的整数。只要缩放因子不超过 38,此方法就有效。如果 s + s2 - s1 超过 38,则会引发错误。
结果缩放的公式是一种选择。Presto 选择了 max(s1, s2)。 其他系统则做出了不同的选择。
Presto 选择 max(s1, s2) 的原因尚不清楚。或许,其思路是假设用户期望的精度是输入小数位数的最大值。然而, 也可以说,期望的精度是被除数的小数位数。在 SQL 中,字面量值的类型由小数点后的实际位数指定。因此,在 下面的 SQL 1.2 中,小数位数为 1,而 0.01 为小数位数 2。有人可能会认为,用户希望以小数点后 2 位的精 度进行操作,因此是 max(s1, s2)。
SELECT 1.2 / 0.01
Modulus
对于模运算:code:a % b,当 a 和 b 为整数时,结果
r 小于 b 且小于或等于 a。因此,表示 r 所需的位数不大于表示 a 或 b 所需位数的最小值。我们可以将此扩展到十进
制输入 a 和 b,方法是计算它们的未缩放值的模数。但是,我们首先应该确保 a 和 b 具有相同的缩放比例。这可以通过将缩放比例较小
的输入放大输入缩放比例之差来实现,因此 a 和 b 都具有缩放比例。一旦 a 和 b 具有相同的标度,我们计算它们的非标度值 A 和 B 的模
数。r 在小数点后有 s 位数字,并且由于 r 所需的位数不超过表示 a 或 b 所需的最小位数,因此结果精度需要增加两个输入的精度和标
度之差中较小的一个。因此,结果类型的精度和标度为:
s = max(s1, s2)
p = min(p2 - s2, p1 - s1) + max(s1, s2)
为了计算 R,我们首先将 A 和 B 重新缩放为“s”:
A = a * 10^s1
B = b * 10^s2
A' = a * 10^s
B' = b * 10^s
然后我们计算重新缩放值的模数:
R = A' % B' = r * 10^s
例如,假设 a = 12.3 且 b = 1.21,则 r = :code:a % b 计算如下:
s = max(1, 2) = 2
p = min(2, 1) + s = 3
A = 12.3 * 10^1 = 123
B = 1.21 * 10^2 = 121
A' = 12.3 * 10^2 = 1230
B' = 1.21 * 10^2 = 121
R = 1230 % 121 = 20 = 0.20 * 100
Decimal Functions
abs(x: decimal(p, s)) -> r: decimal(p, s)
返回 x 的绝对值 (r = `|x|`)。
divide(x: decimal(p1, s1), y: decimal(p2, s2)) -> r: decimal(p, s)
返回 x 除以 y 的结果 (r = x / y)。
x 和 y 是十进制值,其精度和小数位数可能不同。
结果的精度和小数位数计算如下:
p = min(38, p1 + s2 + max(0, s2 - s1))
s = max(s1, s2)
如果 y 为零,或者结果无法使用上面计算的精度表示,或者缩放因子 `max(s1, s2) - s1 + s2` 超过 38,则抛出异常。
floor(x: decimal(p, s)) -> r: decimal(pr, 0)
返回 x 减 y 的结果 (r = x - y)。
x 和 y 是十进制值,精度和小数位数可能不同。
结果的精度和小数位数计算如下:
pr = min(38, p - s + min(s, 1))
minus(x: decimal(p1, s1), y: decimal(p2, s2)) -> r: decimal(p, s)
返回 x 减 y 的结果 (r = x - y)。
x 和 y 是十进制值,精度和小数位数可能不同。
结果的精度和小数位数计算如下:
p = min(38, max(p1 - s1, p2 - s2) + 1 + max(s1, s2))
s = max(s1, s2)
如果结果不能用上面计算的精度来表示,则抛出。
modulus(x: decimal(p1, s1), y: decimal(p2, s2)) -> r: decimal(p, s)
返回 x 除以 y 的余数 (r = x % y)。
x 和 y 是十进制值,精度和小数位数可能不同。结果的精度和小数位数计算如下:
p = min(p2 - s2, p1 - s1) + max(s1, s2)
s = max(s1, s2)
如果 y 为零,则抛出。
multiply(x: decimal(p1, s1), y: decimal(p2, s2)) -> r: decimal(p, s)
返回 x 乘以 y 的结果 (r = x * y)。
x 和 y 是十进制值,其精度和小数位数可能不同。
结果的精度和小数位数计算如下:
p = min(38, p1 + p2)
s = s1 + s2
如果 s1 + s2 超过 38,则不支持该运算。
如果结果无法使用上面计算的精度表示,则抛出异常。
negate(x: decimal(p, s)) -> r: decimal(p, s)
返回 x 的负值 (r = -x)。
plus(x: decimal(p1, s1), y: decimal(p2, s2)) -> r: decimal(p, s)
返回 x 与 y 相加的结果 (r = x + y)。
x 和 y 是十进制值,精度和小数位数可能不同。结果的精度和小数位数计算如下:
p = min(38, max(p1 - s1, p2 - s2) + 1 + max(s1, s2))
s = max(s1, s2)
如果结果不能用上面计算的精度来表示,则抛出。
round(x: decimal(p, s)) -> r: decimal(rp, 0)
返回四舍五入到最接近的整数的“x”。结果的小数部分为 0。
精度计算如下:
pr = min(38, p - s + min(s, 1))
round(x: decimal(p, s), d: integer) -> r: decimal(rp, s)
返回四舍五入到小 数点后 d 位的“x”。结果的小数位数与输入的小数位数相同。精度计算如下:
p = min(38, p + 1)
'd' 可以是正数、零或负数。如果 'd' 超出输入的标度,则返回未修改的 'x'。
SELECT round(123.45, 0); -- 123.00
SELECT round(123.45, 1); -- 123.50
SELECT round(123.45, 2); -- 123.45
SELECT round(123.45, 3); -- 123.45
SELECT round(123.45, -1); -- 120.00
SELECT round(123.45, -2); -- 100.00
SELECT round(123.45, -10); -- 0.00
truncate(x: decimal(p, s)) -> r: decimal(rp, 0)
返回舍去小数点后数字,并将 x 四舍五入为整数的结果。
结果的小数部分为 0。精度计算如下:
pr = max(p - s, 1)
truncate(x: decimal(p, s), d: integer) -> r: decimal(rp, s)
返回截断为小数点后 d 位的 x。
结果的精度和小数位数与输入的精度和小数位数相同。
d 可以是正数、零或负数。
当 d 为负数时,截断小数点左侧的 -d 位。
如果 d 超出输入的小数位数,则返回未修改的 x。
SELECT truncate(999.45, 0); -- 999.00
SELECT truncate(999.45, 1); -- 999.40
SELECT truncate(999.45, 2); -- 999.45
SELECT truncate(999.45, 3); -- 999.45
SELECT truncate(999.45, -1); -- 990.00
SELECT truncate(999.45, -2); -- 900.00
SELECT truncate(999.45, -10); -- 0.00