已发布 上游基线 bf46254 原文 ↗ 在 GitHub 编辑

BigDecimal

BigDecimal 数据类型用于表示任意精度的十进制数。

在 JavaScript 中,数字通常以 64 位浮点数的形式存储。浮点数虽然快速且通用,但会引入细小的舍入误差。这些误差在日常使用中往往难以察觉,但在金融或统计等领域却可能成为问题:细小的不精确随着时间累积,可能导致越来越大的偏差。

通过使用 BigDecimal 模块,你可以避免这些问题,并以更高的精度进行计算。

BigDecimal 数据类型可以表示小数位数很多的实数,从而避免浮点运算中常见的错误(例如 0.1 + 0.2 ≠ 0.3)。

BigDecimal 的工作原理

BigDecimal 用两个组成部分来表示一个数字:

  1. value:一个 BigInt,存储数字的各位数字。
  2. scale:一个 64 位整数,决定小数点的位置。

BigDecimal 所表示的数值按如下公式计算:value × 10-scale

  • 如果 scale 为零或正数,它表示小数点右侧的位数。
  • 如果 scale 为负数,则将 value 乘以 10 的 scale 相反数次幂。

例如:

  • value = 12345nscale = 2BigDecimal 表示 123.45
  • value = 12345nscale = -2BigDecimal 表示 1234500

最大精度很大,但并非无限,限制为 263 位小数。

创建一个 BigDecimal

make

make 函数通过指定一个 BigInt 数值和一个 scale 来创建 BigDecimalscale 决定小数点右侧的位数。

示例(使用指定的 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 创建 BigDecimalscale 默认为 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 }
Avoid Direct Conversion

避免将浮点数直接转换为 BigDecimal,因为其表示形式可能已经引入了精度问题。

基本算术运算

BigDecimal 模块支持多种算术运算,它们能够保证精度,并避免标准 JavaScript 算术中常见的舍入误差。以下是受支持运算的列表:

函数说明
sum将两个 BigDecimal 值相加。
subtract从一个 BigDecimal 值中减去另一个。
multiply将两个 BigDecimal 值相乘。
divide将一个 BigDecimal 值除以另一个,返回 Option<BigDecimal>
unsafeDivide将一个 BigDecimal 值除以另一个;若除数为零则抛出错误。
negateBigDecimal 值取负(即改变其符号)。
remainder返回一个 BigDecimal 值除以另一个的余数,结果为 Option<BigDecimal>
unsafeRemainder返回一个 BigDecimal 值除以另一个的余数;若除数为零则抛出错误。
sign返回 BigDecimal 值的符号(-101)。
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 为 2
  • 1050n,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