Skip to content
// EFFECT DAYS 2026

Rational unstable

Exact rational numbers for ratios that decimal arithmetic cannot represent. A Rational stores a bigint numerator and denominator in lowest terms, so values such as one third survive multiplication, comparison and compounding without rounding. Use BigDecimal for decimal quantities such as money, and Rational when a ratio must stay exact until an explicit conversion.

40 exports Since v4.1.3 Source

Constructors

Since v4.1.3

Converts a BigDecimal to the exact Rational it denotes.

When to use

Use as the exact route from decimal text: 0.1 becomes 1/10, which fromNumber cannot produce.

See

Signature

declare function fromBigDecimal(n: BigDecimal): Rational

Example Converting decimals exactly

import { BigDecimal, Rational } from "effect"
Rational.fromBigDecimal(BigDecimal.fromStringUnsafe("-12.50")) // => Rational.makeUnsafe(-25n, 2n)

fromBigInt

unstable
Since v4.1.3

Creates an integer Rational from a bigint.

Signature

declare function fromBigInt(n: bigint): Rational

Example Creating rationals from bigint

import { Rational } from "effect"
Rational.format(Rational.fromBigInt(-3n)) // => "-3/1"

fromNumber

unstable
Since v4.1.3

Creates a Rational from a finite number, returning Option.none() for NaN, +Infinity and -Infinity.

Details

The result is the exact binary value of the number, not its decimal display. Negative zero becomes 0/1.

Gotchas

0.1 is not one tenth: the nearest double is 3602879701896397 / 2^55, and that is the value this function returns. Use fromBigDecimal with a decimal string when the decimal reading is intended.

See

Signature

declare function fromNumber(n: number): Option<Rational>

Example Creating rationals from numbers

import { Option, Rational } from "effect"
Rational.fromNumber(0.75) // => Option.some(Rational.makeUnsafe(3n, 4n))
Rational.fromNumber(NaN) // => Option.none()
Since v4.1.3

Creates a Rational from a finite number.

Gotchas

Throws a RangeError if the number is not finite. See fromNumber for why 0.1 does not become one tenth.

See

  • fromNumber for returning Option.none when the number is not finite

Signature

declare function fromNumberUnsafe(n: number): Rational

Example Creating rationals from trusted numbers

import { Rational } from "effect"
Rational.fromNumberUnsafe(0.5) // => Rational.makeUnsafe(1n, 2n)

fromString

unstable
Since v4.1.3

Parses an integer or fraction string into a Rational safely.

Details

Accepts [+-]digits or [+-]digits/[+-]digits. Reducible fractions and negative denominators are normalized. Whitespace, decimal points, exponents, the empty string and a zero denominator produce Option.none().

See

Signature

declare function fromString(s: string): Option<Rational>

Example Parsing fraction strings

import { Option, Rational } from "effect"
Rational.fromString("-4/-6") // => Option.some(Rational.makeUnsafe(2n, 3n))
Rational.fromString("3") // => Option.some(Rational.fromBigInt(3n))
Rational.fromString("1.5") // => Option.none()
Since v4.1.3

Parses an integer or fraction string into a Rational.

Gotchas

Throws an Error if the string is not a valid fraction. See fromString for the accepted grammar.

See

  • fromString for returning Option.none on invalid input

Signature

declare function fromStringUnsafe(s: string): Rational

Example Parsing trusted fraction strings

import { Rational } from "effect"
Rational.fromStringUnsafe("6/5") // => Rational.makeUnsafe(6n, 5n)

make

unstable
Since v4.1.3

Creates a Rational from a numerator and denominator, returning Option.none() when the denominator is zero.

Details

The result is reduced to lowest terms with a positive denominator, so make(6n, -4n) and make(-3n, 2n) produce the same value.

See

Signature

declare function make(numerator: bigint, denominator: bigint): Option<Rational>

Example Creating rationals safely

import { Option, Rational } from "effect"
Rational.make(6n, -4n) // => Option.some(Rational.makeUnsafe(-3n, 2n))
Rational.make(1n, 0n) // => Option.none()

makeUnsafe

unstable
Since v4.1.3

Creates a Rational from a numerator and denominator.

Gotchas

Throws a RangeError if the denominator is zero.

See

  • make for returning Option.none on a zero denominator

Signature

declare function makeUnsafe(numerator: bigint, denominator: bigint): Rational

Example Creating rationals from bigint pairs

import { Rational } from "effect"
Rational.format(Rational.makeUnsafe(2n, 6n)) // => "1/3"

Converting

format

unstable
Since v4.1.3

Formats a rational as numerator/denominator.

Details

The output is always a fraction in lowest terms, so integers render as 3/1 and zero as 0/1. It is the inverse of fromString.

Signature

declare function format(n: Rational): string

Example Formatting rationals

import { Rational } from "effect"
Rational.format(Rational.makeUnsafe(12n, 10n)) // => "6/5"
Rational.format(Rational.fromBigInt(0n)) // => "0/1"
Since v4.1.3

Converts a rational to a BigDecimal rounded at the given scale.

Details

scale is the number of decimal places to keep; a negative scale rounds to tens, hundreds and so on. mode defaults to "half-from-zero", matching BigDecimal.round.

Gotchas

Throws a RangeError if scale is not a safe integer.

See

Signature

declare const toBigDecimal: {
(options: {
readonly mode?: BigDecimal.RoundingMode;
readonly scale: number;
}): (self: Rational) => BigDecimal;
(self: Rational, options: {
readonly mode?: BigDecimal.RoundingMode;
readonly scale: number;
}): BigDecimal;
}

Example Rounding rationals to decimals

import { BigDecimal, Rational } from "effect"
const twoThirds = Rational.makeUnsafe(2n, 3n)
Rational.toBigDecimal(twoThirds, { scale: 3 }) // => BigDecimal.fromStringUnsafe("0.667")
Rational.toBigDecimal(twoThirds, { scale: 3, mode: "floor" }) // => BigDecimal.fromStringUnsafe("0.666")
Since v4.1.3

Converts a rational to the nearest JavaScript number.

Details

The ratio is rounded as a whole, so a numerator and denominator that each exceed Number.MAX_VALUE still produce a finite result when their quotient is finite. Every finite double round-trips through fromNumber.

Gotchas

This conversion is unsafe because most rationals are not representable as doubles: the result is rounded, and very large magnitudes become Infinity.

See

Signature

declare function toNumberUnsafe(n: Rational): number

Example Converting rationals to numbers

import { Rational } from "effect"
Rational.toNumberUnsafe(Rational.makeUnsafe(1n, 3n)) // => 1 / 3

Guards

isRational

unstable
Since v4.1.3

Checks whether a given value is a Rational.

Signature

declare function isRational(u: unknown): u is Rational

Example Checking Rational values

import { Rational } from "effect"
Rational.isRational(Rational.fromBigInt(1n)) // => true
Rational.isRational("1/3") // => false

Instances

Equivalence

unstable
Since v4.1.3

An Equivalence instance for rationals. Canonical form makes this a field comparison.

Signature

declare const Equivalence: Equ.Equivalence<Rational>

Order

unstable
Since v4.1.3

An Order instance comparing rationals by exact value.

Signature

declare const Order: order.Order<Rational>

Example Comparing rationals

import { BigDecimal, Rational } from "effect"
const third = Rational.makeUnsafe(1n, 3n)
const nearlyThird = Rational.fromBigDecimal(BigDecimal.fromStringUnsafe("0.3333333333"))
Rational.Order(third, nearlyThird) // => 1

Math

abs

unstable
Since v4.1.3

Returns the absolute value of a rational.

Signature

declare function abs(n: Rational): Rational

clamp

unstable
Since v4.1.3

Restricts a rational to an inclusive range.

Signature

declare const clamp: {
(options: {
maximum: Rational;
minimum: Rational;
}): (self: Rational) => Rational;
(self: Rational, options: {
maximum: Rational;
minimum: Rational;
}): Rational;
}

divide

unstable
Since v4.1.3

Divides self by that, returning Option.none() when that is zero.

See

  • divideUnsafe for division that throws when the divisor is zero

Signature

declare const divide: {
(that: Rational): (self: Rational) => Option<Rational>;
(self: Rational, that: Rational): Option<Rational>;
}

Example Dividing rationals safely

import { Option, Rational } from "effect"
const third = Rational.makeUnsafe(1n, 3n)
Rational.divide(third, Rational.fromBigInt(3n)) // => Option.some(Rational.makeUnsafe(1n, 9n))
Rational.divide(third, Rational.fromBigInt(0n)) // => Option.none()
Since v4.1.3

Divides self by that.

Gotchas

Throws a RangeError if the divisor is zero.

See

  • divide for division that returns Option.none when the divisor is zero

Signature

declare const divideUnsafe: {
(that: Rational): (self: Rational) => Rational;
(self: Rational, that: Rational): Rational;
}

Example Dividing rationals

import { Rational } from "effect"
Rational.divideUnsafe(Rational.makeUnsafe(-2n, 3n), Rational.makeUnsafe(4n, 9n)) // => Rational.makeUnsafe(-3n, 2n)

max

unstable
Since v4.1.3

Returns the larger of two rationals.

Signature

declare const max: {
(that: Rational): (self: Rational) => Rational;
(self: Rational, that: Rational): Rational;
}

min

unstable
Since v4.1.3

Returns the smaller of two rationals.

Signature

declare const min: {
(that: Rational): (self: Rational) => Rational;
(self: Rational, that: Rational): Rational;
}

multiply

unstable
Since v4.1.3

Multiplies two rationals exactly.

Signature

declare const multiply: {
(that: Rational): (self: Rational) => Rational;
(self: Rational, that: Rational): Rational;
}

Example Multiplying rationals

import { Rational } from "effect"
Rational.multiply(Rational.makeUnsafe(1n, 3n), Rational.fromBigInt(3n)) // => Rational.fromBigInt(1n)

multiplyAll

unstable
Since v4.1.3

Multiplies all rationals in an iterable, returning one for an empty collection.

Signature

declare function multiplyAll(collection: Iterable<Rational>): Rational

Example Compounding ratios

import { Rational } from "effect"
Rational.multiplyAll([Rational.makeUnsafe(3n, 2n), Rational.makeUnsafe(4n, 3n)]) // => Rational.fromBigInt(2n)

negate

unstable
Since v4.1.3

Negates a rational.

Signature

declare function negate(n: Rational): Rational

pow

unstable
Since v4.1.3

Raises a rational to an integer power, returning Option.none() for zero raised to a negative exponent.

Details

A negative exponent inverts the fraction before raising it, and any value to the power zero is one, including zero itself.

Gotchas

The exponent must be a safe integer. Anything else, including 0.5, NaN and Infinity, throws a RangeError instead of being rounded.

See

  • powUnsafe for throwing when zero is raised to a negative exponent

Signature

declare const pow: {
(exponent: number): (self: Rational) => Option<Rational>;
(self: Rational, exponent: number): Option<Rational>;
}

Example Raising to integer powers

import { Option, Rational } from "effect"
Rational.pow(Rational.makeUnsafe(1n, 3n), -1) // => Option.some(Rational.fromBigInt(3n))
Rational.pow(Rational.fromBigInt(0n), -1) // => Option.none()

powUnsafe

unstable
Since v4.1.3

Raises a rational to an integer power.

Gotchas

Throws a RangeError for zero raised to a negative exponent, and for an exponent that is not a safe integer.

See

  • pow for returning Option.none when zero is raised to a negative exponent

Signature

declare const powUnsafe: {
(exponent: number): (self: Rational) => Rational;
(self: Rational, exponent: number): Rational;
}

Example Compounding a ratio

import { Rational } from "effect"
// Twelve pure fifths overshoot seven octaves by the Pythagorean comma.
Rational.powUnsafe(Rational.makeUnsafe(3n, 2n), 12) // => Rational.makeUnsafe(531441n, 4096n)

sign

unstable
Since v4.1.3

Returns the sign of a rational as an Ordering.

Signature

declare function sign(n: Rational): Ordering

subtract

unstable
Since v4.1.3

Subtracts that from self exactly.

Signature

declare const subtract: {
(that: Rational): (self: Rational) => Rational;
(self: Rational, that: Rational): Rational;
}

Example Subtracting rationals

import { Rational } from "effect"
Rational.subtract(Rational.fromBigInt(1n), Rational.makeUnsafe(1n, 3n)) // => Rational.makeUnsafe(2n, 3n)

sum

unstable
Since v4.1.3

Adds two rationals exactly.

Signature

declare const sum: {
(that: Rational): (self: Rational) => Rational;
(self: Rational, that: Rational): Rational;
}

Example Adding rationals

import { Rational } from "effect"
Rational.sum(Rational.makeUnsafe(1n, 3n), Rational.makeUnsafe(1n, 6n)) // => Rational.makeUnsafe(1n, 2n)

sumAll

unstable
Since v4.1.3

Sums all rationals in an iterable, returning zero for an empty collection.

Signature

declare function sumAll(collection: Iterable<Rational>): Rational

Example Summing rationals

import { Rational } from "effect"
const third = Rational.makeUnsafe(1n, 3n)
Rational.sumAll([third, third, third]) // => Rational.fromBigInt(1n)

Models

Rational

interface unstable
Since v4.1.3

Represents an exact rational number as a reduced fraction.

Details

Every Rational is kept in canonical form: the numerator and denominator share no common factor, the denominator is positive, and zero is 0/1. Two rationals are equal exactly when their fields are equal.

Signature

interface Rational extends Equal, Pipeable, Inspectable {
readonly "~effect/Rational": "~effect/Rational";
readonly denominator: bigint;
readonly numerator: bigint;
}

Example Inspecting Rational storage

import { Rational } from "effect"
const r = Rational.makeUnsafe(6n, -4n)
r.numerator // => -3n
r.denominator // => 2n

Predicates

between

unstable
Since v4.1.3

Checks whether a rational lies within an inclusive range.

Signature

declare const between: {
(options: {
maximum: Rational;
minimum: Rational;
}): (self: Rational) => boolean;
(self: Rational, options: {
maximum: Rational;
minimum: Rational;
}): boolean;
}

equals

unstable
Since v4.1.3

Checks whether two rationals are equal.

See

  • Equivalence for passing rational equality to APIs that require an Equivalence

Signature

declare const equals: {
(that: Rational): (self: Rational) => boolean;
(self: Rational, that: Rational): boolean;
}
Since v4.1.3

Returns true if the first argument is greater than the second.

Signature

declare const isGreaterThan: {
(that: Rational): (self: Rational) => boolean;
(self: Rational, that: Rational): boolean;
}

Returns true if the first argument is greater than or equal to the second.

Signature

declare const isGreaterThanOrEqualTo: {
(that: Rational): (self: Rational) => boolean;
(self: Rational, that: Rational): boolean;
}

isInteger

unstable
Since v4.1.3

Checks whether a rational is an integer, that is, whether its denominator is one.

Signature

declare function isInteger(n: Rational): boolean

isLessThan

unstable
Since v4.1.3

Returns true if the first argument is less than the second.

Signature

declare const isLessThan: {
(that: Rational): (self: Rational) => boolean;
(self: Rational, that: Rational): boolean;
}
Since v4.1.3

Returns true if the first argument is less than or equal to the second.

Signature

declare const isLessThanOrEqualTo: {
(that: Rational): (self: Rational) => boolean;
(self: Rational, that: Rational): boolean;
}

isNegative

unstable
Since v4.1.3

Checks whether a rational is strictly negative.

Signature

declare function isNegative(n: Rational): boolean

isPositive

unstable
Since v4.1.3

Checks whether a rational is strictly positive.

Signature

declare function isPositive(n: Rational): boolean

isZero

unstable
Since v4.1.3

Checks whether a rational is zero.

Signature

declare function isZero(n: Rational): boolean