BigDecimal
BigDecimal 数据类型用于表示任意精度的十进制数。
在 JavaScript 中,数字通常以 64 位浮点数的形式存储。浮点数虽然快速且通用,但会引入细小的舍入误差。这些误差在日常使用中往往难以察觉,但在金融或统计等领域却可能成为问题:细小的不精确随着时间累积,可能导致越来越大的偏差。
通过使用 BigDecimal 模块,你可以避免这些问题,并以更高的精度进行计算。
BigDecimal 数据类型可以表示小数位数很多的实数,从而避免浮点运算中常见的错误(例如 0.1 + 0.2 ≠ 0.3)。
BigDecimal 的工作原理
BigDecimal 用两个组成部分来表示一个数字:
value:一个BigInt,存储数字的各位数字。scale:一个 64 位整数,决定小数点的位置。
BigDecimal 所表示的数值按如下公式计算:value × 10-scale。
- 如果
scale为零或正数,它表示小数点右侧的位数。 - 如果
scale为负数,则将value乘以 10 的scale相反数次幂。
例如:
value = 12345n、scale = 2的BigDecimal表示123.45。value = 12345n、scale = -2的BigDecimal表示1234500。
最大精度很大,但并非无限,限制为 263 位小数。
创建一个 BigDecimal
make
make 函数通过指定一个 BigInt 数值和一个 scale 来创建 BigDecimal。scale 决定小数点右侧的位数。
示例(使用指定的 scale 创建 BigDecimal)
import { BigDecimal } from "effect"
// Create a BigDecimal from a BigInt (1n) with a scale of 2
const decimal = BigDecimal.make(1n, 2)
console.log(decimal)
// Output: { _id: 'BigDecimal', value: '1', scale: 2 }
// Convert the BigDecimal to a string
console.log(String(decimal))
// Output: BigDecimal(0.01)
// Format the BigDecimal as a standard decimal string
console.log(BigDecimal.format(decimal))
// Output: 0.01
// Convert the BigDecimal to exponential notation
console.log(BigDecimal.toExponential(decimal))
// Output: 1e-2
fromBigInt
fromBigInt 函数根据 bigint 创建 BigDecimal。scale 默认为 0,表示该数字没有小数部分。
示例(从 BigInt 创建 BigDecimal)
import { BigDecimal } from "effect"
const decimal = BigDecimal.fromBigInt(10n)
console.log(decimal)
// Output: { _id: 'BigDecimal', value: '10', scale: 0 }
fromString
将数字字符串解析为 BigDecimal。返回 Option<BigDecimal>:
- 字符串合法时返回
Some(BigDecimal)。 - 字符串非法时返回
None。
示例(将字符串解析为 BigDecimal)
import { BigDecimal } from "effect"
const decimal = BigDecimal.fromString("0.02")
console.log(decimal)
/*
Output:
{
_id: 'Option',
_tag: 'Some',
value: { _id: 'BigDecimal', value: '2', scale: 2 }
}
*/
unsafeFromString
unsafeFromString 函数是 fromString 的变体,当输入字符串非法时会抛出错误。仅当你确信输入始终合法时才使用它。
示例(不安全的字符串解析)
import { BigDecimal } from "effect"
const decimal = BigDecimal.unsafeFromString("0.02")
console.log(decimal)
// Output: { _id: 'BigDecimal', value: '2', scale: 2 }
unsafeFromNumber
根据 JavaScript 的 number 创建 BigDecimal。对于非有限数(NaN、+Infinity 或 -Infinity),会抛出 RangeError。
示例(不安全的数字解析)
import { BigDecimal } from "effect"
console.log(BigDecimal.unsafeFromNumber(123.456))
// Output: { _id: 'BigDecimal', value: '123456', scale: 3 }
避免将浮点数直接转换为 BigDecimal,因为其表示形式可能已经引入了精度问题。
基本算术运算
BigDecimal 模块支持多种算术运算,它们能够保证精度,并避免标准 JavaScript 算术中常见的舍入误差。以下是受支持运算的列表:
| 函数 | 说明 |
|---|---|
sum | 将两个 BigDecimal 值相加。 |
subtract | 从一个 BigDecimal 值中减去另一个。 |
multiply | 将两个 BigDecimal 值相乘。 |
divide | 将一个 BigDecimal 值除以另一个,返回 Option<BigDecimal>。 |
unsafeDivide | 将一个 BigDecimal 值除以另一个;若除数为零则抛出错误。 |
negate | 对 BigDecimal 值取负(即改变其符号)。 |
remainder | 返回一个 BigDecimal 值除以另一个的余数,结果为 Option<BigDecimal>。 |
unsafeRemainder | 返回一个 BigDecimal 值除以另一个的余数;若除数为零则抛出错误。 |
sign | 返回 BigDecimal 值的符号(-1、0 或 1)。 |
abs | 返回 BigDecimal 的绝对值。 |
示例(使用 BigDecimal 执行基本算术运算)
import { BigDecimal } from "effect"
const dec1 = BigDecimal.unsafeFromString("1.05")
const dec2 = BigDecimal.unsafeFromString("2.10")
// Addition
console.log(String(BigDecimal.sum(dec1, dec2)))
// Output: BigDecimal(3.15)
// Multiplication
console.log(String(BigDecimal.multiply(dec1, dec2)))
// Output: BigDecimal(2.205)
// Subtraction
console.log(String(BigDecimal.subtract(dec2, dec1)))
// Output: BigDecimal(1.05)
// Division (safe, returns Option<BigDecimal>)
console.log(BigDecimal.divide(dec2, dec1))
/*
Output:
{
_id: 'Option',
_tag: 'Some',
value: { _id: 'BigDecimal', value: '2', scale: 0 }
}
*/
// Division (unsafe, throws if divisor is zero)
console.log(String(BigDecimal.unsafeDivide(dec2, dec1)))
// Output: BigDecimal(2)
// Negation
console.log(String(BigDecimal.negate(dec1)))
// Output: BigDecimal(-1.05)
// Modulus (unsafe, throws if divisor is zero)
console.log(
String(BigDecimal.unsafeRemainder(dec2, BigDecimal.unsafeFromString("0.6"))),
)
// Output: BigDecimal(0.3)
使用 BigDecimal 进行算术运算有助于避免 JavaScript 中浮点数常见的精度问题。例如:
示例(避免浮点误差)
const dec1 = 1.05
const dec2 = 2.1
console.log(String(dec1 + dec2))
// Output: 3.1500000000000004
比较运算
BigDecimal 模块提供了多个用于比较小数值的函数。借助它们,你可以确定两个值的相对顺序、求最小值或最大值,并检查是否为正值、是否为整数等特定属性。
比较函数
| 函数 | 说明 |
|---|---|
lessThan | 检查第一个 BigDecimal 是否小于第二个。 |
lessThanOrEqualTo | 检查第一个 BigDecimal 是否小于或等于第二个。 |
greaterThan | 检查第一个 BigDecimal 是否大于第二个。 |
greaterThanOrEqualTo | 检查第一个 BigDecimal 是否大于或等于第二个。 |
min | 返回两个 BigDecimal 值中较小的那个。 |
max | 返回两个 BigDecimal 值中较大的那个。 |
示例(比较两个 BigDecimal 值)
import { BigDecimal } from "effect"
const dec1 = BigDecimal.unsafeFromString("1.05")
const dec2 = BigDecimal.unsafeFromString("2.10")
console.log(BigDecimal.lessThan(dec1, dec2))
// Output: true
console.log(BigDecimal.lessThanOrEqualTo(dec1, dec2))
// Output: true
console.log(BigDecimal.greaterThan(dec1, dec2))
// Output: false
console.log(BigDecimal.greaterThanOrEqualTo(dec1, dec2))
// Output: false
console.log(BigDecimal.min(dec1, dec2))
// Output: { _id: 'BigDecimal', value: '105', scale: 2 }
console.log(BigDecimal.max(dec1, dec2))
// Output: { _id: 'BigDecimal', value: '210', scale: 2 }
用于比较的谓词
该模块还包含用于检查 BigDecimal 特定属性的谓词:
| 谓词 | 说明 |
|---|---|
isZero | 检查该值是否恰好为零。 |
isPositive | 检查该值是否为正数。 |
isNegative | 检查该值是否为负数。 |
between | 检查该值是否落在指定范围内(含边界)。 |
isInteger | 检查该值是否为整数(即没有小数部分)。 |
示例(检查 BigDecimal 值的符号与属性)
import { BigDecimal } from "effect"
const dec1 = BigDecimal.unsafeFromString("1.05")
const dec2 = BigDecimal.unsafeFromString("-2.10")
console.log(BigDecimal.isZero(BigDecimal.unsafeFromString("0")))
// Output: true
console.log(BigDecimal.isPositive(dec1))
// Output: true
console.log(BigDecimal.isNegative(dec2))
// Output: true
console.log(
BigDecimal.between({
minimum: BigDecimal.unsafeFromString("1"),
maximum: BigDecimal.unsafeFromString("2"),
})(dec1),
)
// Output: true
console.log(
BigDecimal.isInteger(dec2),
BigDecimal.isInteger(BigDecimal.fromBigInt(3n)),
)
// Output: false true
规范化与相等性
在某些情况下,两个 BigDecimal 值可能具有不同的内部表示,却仍然表示同一个数字。
例如,1.05 在内部可以用不同的 scale 表示,比如:
105n,scale 为21050n,scale 为3
为了保证一致性,你可以对 BigDecimal 进行规范化,以调整 scale 并去除末尾的零。
规范化
BigDecimal.normalize 函数会调整 BigDecimal 的 scale,并消除其内部表示中不必要的末尾零。
示例(规范化 BigDecimal)
import { BigDecimal } from "effect"
const dec = BigDecimal.make(1050n, 3)
console.log(BigDecimal.normalize(dec))
// Output: { _id: 'BigDecimal', value: '105', scale: 2 }
相等性
若要检查两个 BigDecimal 值在数值上是否相等(无论其内部表示如何),请使用 BigDecimal.equals 函数。
示例(检查相等性)
import { BigDecimal } from "effect"
const dec1 = BigDecimal.make(105n, 2)
const dec2 = BigDecimal.make(1050n, 3)
console.log(BigDecimal.equals(dec1, dec2))
// Output: true