Rational Demo — The Power of Arbitrary-Precision Rationals

Rational is a rational number type where both the numerator and denominator are arbitrary-precision Int values. Unlike floating-point numbers, it is completely free of rounding errors, enabling exact solutions that are impossible with numerical computation.

This page presents 5 demos showcasing the appeal of arbitrary-precision rational numbers. All outputs below were actually computed by calx. The source code is provided at the bottom of the page; you can build calx and verify the same results on your own machine.

Demo 1: The Floating-Point Trap vs Exact Rationals

double cannot represent the decimal 0.1 exactly. Internally it becomes the infinite repeating binary fraction $0.1 = 0.0001100110011\ldots_2$, so rounding errors always occur. Rational(1, 10) stores numerator 1 and denominator 10 as-is, so the error is zero.

[double] 0.1 + 0.2 == 0.3 ? FALSE 0.1 + 0.2 = 3.00000000000000044409e-01 0.3 = 2.99999999999999988898e-01 [Rational] 1/10 + 2/10 == 3/10 ? true 1/10 + 2/10 = 3/10

Next, we compute the harmonic series $H_{100} = \sum_{k=1}^{100} 1/k$ in 3 different ways. With double, the result depends on summation order, but with Rational, we get the exact value.

Harmonic number H_100 = 1 + 1/2 + 1/3 + ... + 1/100: double (forward sum): 5.18737751763962063e+00 double (reverse sum): 5.18737751763962152e+00 Rational (exact) : 5.18737751763962026 Digits in exact numerator: 41 digits Digits in exact denominator: 40 digits
With double, the forward and reverse sums differ in the last 2 digits. The exact rational value has a numerator and denominator each exceeding 40 digits, which cannot be fully represented in floating point.

Demo 2: Best Rational Approximations of Pi via Continued Fractions

Any real number can be expanded as a continued fraction:

$$\pi = 3 + \cfrac{1}{7 + \cfrac{1}{15 + \cfrac{1}{1 + \cfrac{1}{292 + \cdots}}}}$$

Truncating a continued fraction yields the best rational approximation for a given denominator size. calx's Rational::fromContinuedFraction() and toContinuedFraction() make this easy to compute.

pi = [3; 7, 15, 1, 292, 1, 1, 1, 2, 1, 3, 1, 14, ...] Terms Approximation Decimal value Correct digits ----------------------------------------------------------------------- 1 3/1 3 0 digits 2 22/7 3.14285714285714285714 2 digits 3 333/106 3.14150943396226415094 4 digits 4 355/113 3.14159292035398230088 6 digits 5 103993/33102 3.14159265301190260407 9 digits 6 104348/33215 3.14159265392142104471 9 digits 7 208341/66317 3.14159265346743670552 9 digits 8 312689/99532 3.14159265361893662340 9 digits 9 833719/265381 3.14159265358107777120 11 digits 10 1146408/364913 3.14159265359140397848 10 digits 11 4272943/1360120 3.14159265358938917154 12 digits 12 5419351/1725033 3.14159265358981538324 12 digits 13 80143857/25510582 3.14159265358979265938 14 digits
355/113 achieves 6 correct decimal digits with only a 3-digit denominator — discovered by the 5th-century Chinese mathematician Zu Chongzhi. The large next term 292 mathematically explains why 355/113 is such an "exceptionally good approximation." The larger the next quotient in a continued fraction, the better the preceding convergent.

Demo 3: Harmonic Numbers and Bernoulli Numbers

Harmonic numbers $H_n = \sum_{k=1}^{n} 1/k$ and Bernoulli numbers $B_n$ are constants that appear frequently in number theory and analysis. Both are rational numbers, so Rational can produce their exact values.

Harmonic Numbers $H_n$

H_ 1 = 1... = 1 H_ 5 = 2.283333333333... = 137/60 H_10 = 2.928968253968... = 7381/2520 H_20 = 3.597739657144... H_50 = 4.499205338329...

Bernoulli Numbers $B_n$

The Bernoulli numbers satisfy $B_0 = 1, B_1 = -1/2$, and $B_n = 0$ for odd $n > 1$. The even-indexed Bernoulli numbers play an important role in number theory. The numerator 691 appearing in $B_{12} = -691/2730$ is a famous prime that also appears in Ramanujan's $\tau$ function.

B_ 0 = 1 B_ 1 = -1/2 B_ 2 = 1/6 B_ 4 = -1/30 B_ 6 = 1/42 B_ 8 = -1/30 B_10 = 5/66 B_12 = -691/2730 B_20 = -174611/330 B_30 = 8615841276005/14322

Demo 4: The Stern-Brocot Tree and Farey Sequences

Every positive rational number appears exactly once in the Stern-Brocot tree, a binary tree structure. The Farey sequence $F_Q$ lists all irreducible fractions with denominator at most $Q$ in ascending order. Both are fundamental structures in number theory, and calx provides enumeration and search functions.

Stern-Brocot Order (ascending by height)

1, 1/2, 2, 1/3, 2/3, 3, 3/2, 1/4, 3/4, 4, 4/3, 1/5, 2/5, 3/5, 4/5, 5, 5/2, 5/3, 5/4, 1/6, ...

Calkin-Wilf Sequence

The Calkin-Wilf sequence enumerates all positive rationals using the simple rule: "the successor of $p/q$ is $1/(2\lfloor p/q \rfloor + 1 - p/q)$."

1, 1/2, 2, 1/3, 3/2, 2/3, 3, 1/4, 4/3, 3/5, 5/2, 2/5, 5/3, 3/4, 4, ...

Farey Sequence $F_7$

0, 1/7, 1/6, 1/5, 1/4, 2/7, 1/3, 2/5, 3/7, 1/2, 4/7, 3/5, 2/3, 5/7, 3/4, 4/5, 5/6, 6/7, 1 Total: 19 irreducible fractions

simplestBetween: The Simplest Rational Between Two Given Rationals

Finds the rational with the smallest denominator in the open interval $(a, b)$. This is computed efficiently using the structure of the Stern-Brocot tree.

simplestBetween(2/5, 3/7) = 5/12 simplestBetween(1/3, 1/2) = 2/5 simplestBetween(7/10, 5/7) = 12/17 simplestBetween(99/100, 100/101) = 199/201
The simplest rational between 99/100 and 100/101 is 199/201, not the intuitive "199/200" (which actually lies outside this interval).

Source Code and How to Run

All output on this page was generated from the following C++ program. Build calx and you can verify the same results on your own machine.

example_rational.cpp (click to expand)
// Copyright (C) 2026 Kiyotsugu Arai
// SPDX-License-Identifier: LGPL-3.0-or-later

// example_rational.cpp
// Arbitrary-precision rational demo: 4 scenarios showcasing Rational
//
// Build:
//   cl /std:c++latest /EHsc /O2 /MD /utf-8 /I<calx>/include examples\example_rational.cpp
//      /link calx_rational.lib calx_float.lib calx_int.lib /MACHINE:X64

#include <math/core/mp/Rational.hpp>
#include <math/core/mp/Rational/RationalConstants.hpp>
#include <iostream>
#include <iomanip>
#include <string>
#include <cmath>
#include <vector>

using namespace calx;

// ============================================================================
// Demo 1: Floating-Point Trap vs Exact Rational
//   — Common computations where double fails
// ============================================================================
static void demo_float_trap() {
    std::cout << "============================================================\n";
    std::cout << " Demo 1: Floating-Point Trap vs Exact Rational\n";
    std::cout << "============================================================\n\n";

    // 0.1 + 0.2 == 0.3 ?
    {
        double da = 0.1, db = 0.2, dc = 0.3;
        Rational ra(1, 10), rb(2, 10), rc(3, 10);

        std::cout << "  [double]   0.1 + 0.2 == 0.3 ? "
                  << ((da + db == dc) ? "true" : "FALSE") << std::endl;
        std::cout << std::setprecision(20);
        std::cout << "             0.1 + 0.2 = " << (da + db) << std::endl;
        std::cout << "             0.3       = " << dc << std::endl;

        std::cout << "  [Rational] 1/10 + 2/10 == 3/10 ? "
                  << (((ra + rb) <=> rc) == 0 ? "true" : "FALSE") << std::endl;
        std::cout << "             1/10 + 2/10 = " << (ra + rb) << "\n\n";
    }

    // H_100 via forward sum vs reverse sum vs exact rational
    {
        constexpr int N = 100;

        // double: forward sum (1 -> N)
        double fwd = 0.0;
        for (int k = 1; k <= N; ++k) fwd += 1.0 / k;

        // double: reverse sum (N -> 1)
        double bwd = 0.0;
        for (int k = N; k >= 1; --k) bwd += 1.0 / k;

        // Rational: exact value
        Rational exact = harmonicNumber(N);

        std::cout << "  Harmonic number H_" << N << " = 1 + 1/2 + 1/3 + ... + 1/" << N << ":\n";
        std::cout << std::setprecision(17);
        std::cout << "    double (forward sum): " << fwd << std::endl;
        std::cout << "    double (reverse sum): " << bwd << std::endl;
        std::cout << "    Rational (exact)    : " << exact.toDecimal(17) << std::endl;
        std::cout << "    Digits in exact numerator:   " << exact.numerator().toString().size() << " digits\n";
        std::cout << "    Digits in exact denominator: " << exact.denominator().toString().size() << " digits\n";
    }
    std::cout << std::endl;
}

// ============================================================================
// Demo 2: Continued Fractions — Best Approximations of Pi
//   — Why is 355/113 so remarkably accurate?
// ============================================================================
static void demo_continued_fraction() {
    std::cout << "============================================================\n";
    std::cout << " Demo 2: Continued Fractions — Best Approximations of Pi\n";
    std::cout << "============================================================\n\n";

    // Continued fraction expansion of pi: [3; 7, 15, 1, 292, 1, 1, 1, 2, ...]
    std::vector<Int> pi_cf = {
        Int(3), Int(7), Int(15), Int(1), Int(292),
        Int(1), Int(1), Int(1), Int(2), Int(1),
        Int(3), Int(1), Int(14)
    };

    // Reference value of pi (sufficient digits for comparison)
    const std::string pi_str = "3.14159265358979323846264338327950288";

    std::cout << "  pi = [3; 7, 15, 1, 292, 1, 1, 1, 2, 1, 3, 1, 14, ...]\n\n";
    std::cout << std::setw(6) << "Terms" << "  "
              << std::setw(20) << "Approximation" << "  "
              << std::setw(24) << "Decimal value" << "  "
              << "Correct digits" << std::endl;
    std::cout << std::string(75, '-') << std::endl;

    for (size_t depth = 1; depth <= pi_cf.size(); ++depth) {
        // Reconstruct rational from first depth terms of the continued fraction
        std::vector<Int> partial(pi_cf.begin(), pi_cf.begin() + depth);
        Rational approx = Rational::fromContinuedFraction(partial);

        // Decimal expansion
        std::string dec = approx.toDecimal(20);

        // Count correct digits (after decimal point)
        int correct = 0;
        size_t start = 2;  // after "3."
        for (size_t i = start; i < std::min(dec.size(), pi_str.size()); ++i) {
            if (dec[i] == pi_str[i]) ++correct;
            else break;
        }

        // Fraction display
        std::string frac = approx.numerator().toString() + "/"
                         + approx.denominator().toString();

        std::cout << std::setw(6) << depth << "  "
                  << std::setw(20) << frac << "  "
                  << std::setw(24) << dec << "  "
                  << correct << " digits" << std::endl;
    }

    std::cout << "\n  Note: Adding term [292] dramatically improves precision from 355/113.\n";
    std::cout << "  The larger the quotient, the better the preceding approximation.\n";
    std::cout << "  355/113 achieves 6 decimal digits with just a 3-digit denominator —\n";
    std::cout << "  discovered by Zu Chongzhi in 5th-century China.\n";
    std::cout << std::endl;
}

// ============================================================================
// Demo 3: Harmonic Numbers & Bernoulli Numbers
//   — Number-theoretic gems computed with arbitrary-precision rationals
// ============================================================================
static void demo_harmonic_bernoulli() {
    std::cout << "============================================================\n";
    std::cout << " Demo 3: Harmonic Numbers & Bernoulli Numbers\n";
    std::cout << "============================================================\n\n";

    // Harmonic numbers
    std::cout << "  --- Harmonic numbers H_n (exact values) ---\n";
    for (int n : {1, 5, 10, 20, 50}) {
        Rational Hn = harmonicNumber(n);
        std::cout << "  H_" << std::setw(2) << n << " = "
                  << std::setw(8) << Hn.toDecimal(12) << "...";
        if (n <= 10) {
            std::cout << "  = " << Hn;
        }
        std::cout << std::endl;
    }

    // Bernoulli numbers
    std::cout << "\n  --- Bernoulli numbers B_n (exact values) ---\n";
    std::cout << "  (B_1 = -1/2; B_n = 0 for odd n > 1)\n\n";
    for (int n : {0, 1, 2, 4, 6, 8, 10, 12, 20, 30}) {
        Rational Bn = bernoulli(n);
        std::cout << "  B_" << std::setw(2) << n << " = " << Bn << std::endl;
    }

    std::cout << "\n  The numerator and denominator of B_30 are large integers:\n";
    Rational B30 = bernoulli(30);
    std::cout << "    Numerator:   " << B30.numerator() << std::endl;
    std::cout << "    Denominator: " << B30.denominator() << std::endl;

    std::cout << std::endl;
}

// ============================================================================
// Demo 4: Stern-Brocot Tree & Farey Sequence
//   — Exploring the beautiful structure of rational numbers
// ============================================================================
static void demo_stern_brocot_farey() {
    std::cout << "============================================================\n";
    std::cout << " Demo 4: Stern-Brocot Tree & Farey Sequence\n";
    std::cout << "============================================================\n\n";

    // Stern-Brocot tree: enumerate via nextMinimal
    std::cout << "  --- Stern-Brocot order (first 20 by ascending height) ---\n  ";
    {
        Rational x(1, 1);
        std::cout << x;
        for (int i = 1; i < 20; ++i) {
            x = nextMinimal(x);
            std::cout << ", " << x;
        }
        std::cout << ", ...\n\n";
    }

    // Calkin-Wilf sequence
    std::cout << "  --- Calkin-Wilf sequence (first 15) ---\n  ";
    {
        Rational x(1, 1);
        std::cout << x;
        for (int i = 1; i < 15; ++i) {
            x = nextCalkinWilf(x);
            std::cout << ", " << x;
        }
        std::cout << ", ...\n\n";
    }

    // Farey sequence F_7
    std::cout << "  --- Farey sequence F_7 (irreducible fractions with denominator <= 7) ---\n  ";
    {
        std::vector<Rational> farey;
        farey.push_back(Rational(0, 1));
        farey.push_back(Rational(1, 7));

        // Enumerate 0 < p/q <= 1, q <= 7 using nextMinimal
        Rational x(0, 1);
        farey.clear();
        farey.push_back(x);
        for (;;) {
            auto [left, right] = fareyNeighbors(x, Int(7));
            if (right > Rational(1)) break;
            farey.push_back(right);
            x = right;
        }

        for (size_t i = 0; i < farey.size(); ++i) {
            if (i > 0) std::cout << ", ";
            std::cout << farey[i];
        }
        std::cout << "\n";
        std::cout << "  Total: " << farey.size() << " irreducible fractions\n\n";
    }

    // simplestBetween
    std::cout << "  --- simplestBetween: simplest rational between two given rationals ---\n";
    {
        struct { Rational a, b; } cases[] = {
            {Rational(3, 7),  Rational(2, 5)},
            {Rational(1, 3),  Rational(1, 2)},
            {Rational(7, 10), Rational(5, 7)},
            {Rational(99, 100), Rational(100, 101)},
        };
        for (auto& [a, b] : cases) {
            Rational lo = (a < b) ? a : b;
            Rational hi = (a < b) ? b : a;
            Rational s = simplestBetween(lo, hi);
            std::cout << "    simplestBetween(" << lo << ", " << hi << ") = " << s
                      << std::endl;
        }
    }

    std::cout << std::endl;
}

// ============================================================================
// main
// ============================================================================
int main() {
    std::cout << "###########################################################\n";
    std::cout << "#  calx Rational Demo — Arbitrary-Precision Rationals      #\n";
    std::cout << "###########################################################\n\n";

    demo_float_trap();
    demo_continued_fraction();
    demo_harmonic_bernoulli();
    demo_stern_brocot_farey();

    std::cout << "###########################################################\n";
    std::cout << "#  All demos completed                                     #\n";
    std::cout << "###########################################################\n";

    return 0;
}

For API details, see the Rational API Reference.

Build and Run

cd calx
mkdir build && cd build
cmake .. -G "Visual Studio 17 2022" -A x64
cmake --build . --config Release --target example-rational
examples\Release\example-rational.exe