BigDecimal
In JavaScript, numbers are typically stored as 64-bit floating-point values. While floating-point numbers are fast and versatile, they can introduce small rounding errors. These are often hard to notice in everyday usage but can become problematic in areas like finance or statistics, where small inaccuracies may lead to larger discrepancies over time.
By using the BigDecimal module, you can avoid these issues and perform calculations with a higher degree of precision.
The BigDecimal data type can represent real numbers with a large number of decimal places, preventing the common errors of floating-point math (for example, 0.1 + 0.2 โ 0.3).
How BigDecimal Works
A BigDecimal represents a number using two components:
value: ABigIntthat stores the digits of the number.scale: A 64-bit integer that determines the position of the decimal point.
The number represented by a BigDecimal is calculated as: value x 10-scale.
- If
scaleis zero or positive, it specifies the number of digits to the right of the decimal point. - If
scaleis negative, thevalueis multiplied by 10 raised to the power of the negated scale.
For example:
- A
BigDecimalwithvalue = 12345nandscale = 2represents123.45. - A
BigDecimalwithvalue = 12345nandscale = -2represents1234500.
The maximum precision is large but not infinite, limited to 263 decimal places.
Creating a BigDecimal
make
The make function creates a BigDecimal by specifying a BigInt value and a scale. The scale determines the number of digits to the right of the decimal point.
Example (Creating a BigDecimal with a Specified Scale)
import { BigDecimal } from "effect"
// Create a BigDecimal from a BigInt (1n) with a scale of 2const decimal = BigDecimal.make(1n, 2)
console.log(decimal)// Output: { _id: 'BigDecimal', value: '1', scale: 2 }
// Convert the BigDecimal to a stringconsole.log(String(decimal))// Output: BigDecimal(0.01)
// Format the BigDecimal as a standard decimal stringconsole.log(BigDecimal.format(decimal))// Output: 0.01
// Convert the BigDecimal to exponential notationconsole.log(BigDecimal.toExponential(decimal))// Output: 1e-2fromBigInt
The fromBigInt function creates a BigDecimal from a bigint. The scale defaults to 0, meaning the number has no fractional part.
Example (Creating a BigDecimal from a BigInt)
import { BigDecimal } from "effect"
const decimal = BigDecimal.fromBigInt(10n)
console.log(decimal)// Output: { _id: 'BigDecimal', value: '10', scale: 0 }fromString
Parses a numerical string into a BigDecimal. Returns an Option<BigDecimal>:
Some(BigDecimal)if the string is valid.Noneif the string is invalid.
Example (Parsing a String into a 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
The unsafeFromString function is a variant of fromString that throws an error if the input string is invalid. Use this only when you are confident that the input will always be valid.
Example (Unsafe Parsing of a String)
import { BigDecimal } from "effect"
const decimal = BigDecimal.unsafeFromString("0.02")
console.log(decimal)// Output: { _id: 'BigDecimal', value: '2', scale: 2 }unsafeFromNumber
Creates a BigDecimal from a JavaScript number. Throws a RangeError for non-finite numbers (NaN, +Infinity, or -Infinity).
Example (Unsafe Parsing of a Number)
import { BigDecimal } from "effect"
console.log(BigDecimal.unsafeFromNumber(123.456))// Output: { _id: 'BigDecimal', value: '123456', scale: 3 }Basic Arithmetic Operations
The BigDecimal module supports a variety of arithmetic operations that provide precision and avoid the rounding errors common in standard JavaScript arithmetic. Below is a list of supported operations:
| Function | Description |
|---|---|
sum |
Adds two BigDecimal values. |
subtract |
Subtracts one BigDecimal value from another. |
multiply |
Multiplies two BigDecimal values. |
divide |
Divides one BigDecimal value by another, returning an Option<BigDecimal>. |
unsafeDivide |
Divides one BigDecimal value by another, throwing an error if the divisor is zero. |
negate |
Negates a BigDecimal value (i.e., changes its sign). |
remainder |
Returns the remainder of dividing one BigDecimal value by another, returning an Option<BigDecimal>. |
unsafeRemainder |
Returns the remainder of dividing one BigDecimal value by another, throwing an error if the divisor is zero. |
sign |
Returns the sign of a BigDecimal value (-1, 0, or 1). |
abs |
Returns the absolute value of a BigDecimal. |
Example (Performing Basic Arithmetic with BigDecimal)
import { BigDecimal } from "effect"
const dec1 = BigDecimal.unsafeFromString("1.05")const dec2 = BigDecimal.unsafeFromString("2.10")
// Additionconsole.log(String(BigDecimal.sum(dec1, dec2)))// Output: BigDecimal(3.15)
// Multiplicationconsole.log(String(BigDecimal.multiply(dec1, dec2)))// Output: BigDecimal(2.205)
// Subtractionconsole.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)
// Negationconsole.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)Using BigDecimal for arithmetic operations helps to avoid the inaccuracies commonly encountered with floating-point numbers in JavaScript. For example:
Example (Avoiding Floating-Point Errors)
const dec1 = 1.05const dec2 = 2.1
console.log(String(dec1 + dec2))// Output: 3.1500000000000004Comparison Operations
The BigDecimal module provides several functions for comparing decimal values. These allow you to determine the relative order of two values, find the minimum or maximum, and check specific properties like positivity or integer status.
Comparison Functions
| Function | Description |
|---|---|
lessThan |
Checks if the first BigDecimal is smaller than the second. |
lessThanOrEqualTo |
Checks if the first BigDecimal is smaller than or equal to the second. |
greaterThan |
Checks if the first BigDecimal is larger than the second. |
greaterThanOrEqualTo |
Checks if the first BigDecimal is larger than or equal to the second. |
min |
Returns the smaller of two BigDecimal values. |
max |
Returns the larger of two BigDecimal values. |
Example (Comparing Two BigDecimal Values)
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 }Predicates for Comparison
The module also includes predicates to check specific properties of a BigDecimal:
| Predicate | Description |
|---|---|
isZero |
Checks if the value is exactly zero. |
isPositive |
Checks if the value is positive. |
isNegative |
Checks if the value is negative. |
between |
Checks if the value lies within a specified range (inclusive). |
isInteger |
Checks if the value is an integer (i.e., no fractional part). |
Example (Checking the Sign and Properties of BigDecimal Values)
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 trueNormalization and Equality
In some cases, two BigDecimal values can have different internal representations but still represent the same number.
For example, 1.05 could be internally represented with different scales, such as:
105nwith a scale of21050nwith a scale of3
To ensure consistency, you can normalize a BigDecimal to adjust the scale and remove trailing zeros.
Normalization
The BigDecimal.normalize function adjusts the scale of a BigDecimal and eliminates any unnecessary trailing zeros in its internal representation.
Example (Normalizing a BigDecimal)
import { BigDecimal } from "effect"
const dec = BigDecimal.make(1050n, 3)
console.log(BigDecimal.normalize(dec))// Output: { _id: 'BigDecimal', value: '105', scale: 2 }Equality
To check if two BigDecimal values are numerically equal, regardless of their internal representation, use the BigDecimal.equals function.
Example (Checking Equality)
import { BigDecimal } from "effect"
const dec1 = BigDecimal.make(105n, 2)const dec2 = BigDecimal.make(1050n, 3)
console.log(BigDecimal.equals(dec1, dec2))// Output: true