Rebol 编程/Rebol3 金额数据类型
关于金额数据类型 - 高精度算术用于金融和科学应用。
在 Rebol3 中,金额数据类型使用编码十进制表示法,允许精确的数字表示,长度最长可达 26 位小数。由于其精度,此数据类型对于金融、银行、商业、交易,甚至某些类型的科学应用非常有用。
从外部来看,金额的表示方式与 R2 中相同。在代码和数据中,$ 表示正在指定数据类型。
$1.23 -$1.23
与 R2 不同的是,金额保持高达 26 位小数的精度。
>> $123456789012345678901234.56 + $0.01 == $123456789012345678901234.57
将此与使用 Rebol 十进制! (64 位 IEEE754 二进制浮点数) 值时发生的情况进行比较。
>> 123456789012345678901234.56 + 0.01 == 1.2345678901234569E+23
原始数字中的许多位数都丢失了,并且 0.01 的加法在舍入中完全丢失了。
如以下示例所示,甚至可以更早地看到差异。
>> $0.30 - $0.20 - $0.10 == $0
使用 Rebol 十进制! 数据类型时,我们得到
>> 0.30 - 0.20 - 0.10 == -2.77555756156289E-17
造成这种情况的原因是,像 0.30、0.20 或 0.10 这样的数字可以用 money! 数据类型精确表示,而它们不能用 Rebol 十进制! (64 位 IEEE754 二进制浮点数) 数据类型精确表示。
(此属性是金额数据类型在需要精度的金融和交易应用中很有用。)
此外,在进行一些需要高达 26 位小数的整数运算时,也可以使用它,例如在此示例中(精确计算 2 ** 64,这在 Rebol 整数格式中无法完成)。
>> a: $1 loop 64 [a: a * $2] == $18446744073709551616
可以获得高达 2 ** 86 的精确结果,2 ** 87 是第一个需要超过 26 位小数的 2 的幂。
请参阅范围表。
金额数据类型使用高精度算术(也称为 bignum 算术)的变体,使用比典型 BCD(二进制编码十进制)更有效的实现。
空间效率比较:虽然 BCD 被定义为使用 4 位来表示一位十进制数字,即 26 位十进制数字需要 104 位,但金额数据类型为此目的仅使用 87 位。
主要的是,数据类型旨在利用所有现代 CPU 上的高性能 32 位整数运算。此外,数据类型针对直接存储在 Rebol 的 128 位处理引擎中进行了优化,消除了标准数值计算中的内存分配和 GC 的开销。
金额数字的表示使用符号、无符号有效数字(也称为系数/尾数)以小端序形式表示,具有 26 位十进制数字,以及范围在 -128 到 +127 之间的无偏带符号十进制指数。
96 位二进制表示由以下部分组成:
- 87 位用于无符号有效数字(也称为系数/尾数)
- 1 位符号
- 8 位用于无偏带符号十进制指数
使用每位十进制数字四位的 BCD 表示法以其非常快地转换为字符串表示和十进制移位而闻名。其他操作非常慢。与之相反,为金额数据类型选择的表示法应该对通常的算术运算更快。
金额数据类型支持这些操作。
- 添加
- 减法
- 乘法
- 除法
- 余数
- 否定
- 绝对值
- 舍入
- 相同?
- 相等?
- 严格相等?
- 不等?
- 严格不等?
- 大于?
- 小于?
- 大于或等于?
- 小于或等于?
- 负数?
- 正数?
- 零?
- 最小值
- 最大值
此外,还支持所有相应的 infix 运算符。
支持这些转换。
- 字符串到金额
- 金额到字符串
- 整数到金额
- 金额到整数
- 十进制到金额
- 金额到十进制
- 二进制到十进制
- 十进制到二进制
当金额类型与其他数字数据类型混合使用在函数中时,也会发生自动转换。金额被认为是“更强的类型” - 产生结果的数据类型。
>> $1 + 1 == $2 >> $1 + 1.0 == $2 >> 1 + $1 == $2 >> 1.0 + $1 == $2 >> 1.2 > $1 == true
如上所述,money! 数据类型是一种高精度数据类型。从字符串到金额的转换是精确的,保持建议的精度,如上所示。只有当为金额值提供超过 26 位小数时,才会发生精度损失。
从金额到字符串的转换是精确的。
从整数到金额的转换是精确的,money! 数据类型可以精确表示每个 64 位整数。
从货币到整数的转换对于适合 64 位整数范围的整数值是精确的。例如
>> to integer! $100 == 100
如果值不适合整数范围,则会发生溢出。例如
>> to integer! $9'999'999'999'999'999'999 ** Math error: math or number overflow ** Where: to ** Near: to integer! $9999999999999999999
如果货币值不是整数,则会被截断。例如
>> to integer! $1.50 == 1
从小数到货币的转换通常是精确的,因为货币数据类型比小数数据类型更精确。为了确保小数值被精确地表示(即它被唯一确定),转换使用 17 位小数。例如
>> a: 0.1 == 0.10000000000000001 >> b: to money! a == $0.10000000000000001 >> same? a to decimal! b == true
但是,可能会发生溢出,因为 decimal! 数据类型的范围大于 money! 数据类型的范围。当转换微小的 decimal! 值时,如果小数指数低于 -128,也可能会发生精度损失(下溢),导致 $0。
既不会发生溢出,也不会发生下溢,但小数值不能精确地表示某些货币值。例如
>> a: $0.01 == $0.01 >> b: to decimal! a == 0.10000000000000001 >> to money! b == $0.10000000000000001 >> same? a to money! b == false
- 货币值旨在保持精度(它们没有被归一化)。例如
>> a: $10 == $10 >> b: $10.00 == $10.00
如果需要不同的精度,我们可以使用 **round** 操作。例如
>> round/to $1.000 $0.01 == $1.00
在上面的例子中,我们将数字舍入到较低的精度(美分)。如果需要,我们可以使用 ROUND 操作来获得更高的精度。
>> round/to $1.1 $0.01 == $1.10
在上面的例子中,我们将数字舍入到更高的精度(美分)。
不建议将 **scale** 参数作为小数值提供,因为我们经常无法获得准确的结果。例如
>> round/to $1.15 0.1 == $1.10000000000000011
,这很可能不是我们想要的。如果我们使用作为货币值提供的准确 **scale**,我们将获得
>> round/to $1.15 $0.10 == $1.20
还要注意,使用 $0.10 作为比例,而不是 $0.1!是有意义的。
乘法和加法使用其操作数的精度。例如
>> $10 + $10.50 == $20.50
>> $2 * $1.10 == $2.20
如果需要不同的精度,我们可以像往常一样使用 **round** 操作。
与舍入比例类似,不建议使用小数值作为系数。在可能的情况下,最好使用货币值作为系数,因为它具有更高的准确性优势。
与上面不同,除法总是生成所有 26 位数字以提供可用的全部精度。想法是,当除以货币值时,我们通常必须根据某些标准(这可能取决于情况)来舍入结果。结果包含这么多的数字,因此根据目前使用的任何标准来舍入它没有问题,并且 **round** 操作有很多细化可供用户选择。
>> $2 / $3 == $0.66666666666666666666666667
>> $3 / 1.5 == $2.0000000000000000000000000
与 Rebol2 不同,在 Rebol3 中,货币单位没有实现,这意味着所有 Rebol3 货币值目前都是无单位的。