Float Demo — The Power of Arbitrary-Precision Floating Point

Float is a floating-point type whose mantissa is represented by an arbitrary-precision Int. It breaks through the roughly 15-digit limit of double, enabling computations with hundreds of thousands of digits or more.

This page presents 5 demos showcasing the power of arbitrary-precision floating point. 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: Mathematical Constants to 100 Digits

Float provides built-in functions for computing major mathematical constants. Here we compute 7 constants to 100-digit precision and measure the time.

sqrt(2) = 1.4142135623730950488016887242096980785696718753769480731766 797379907324784621070388503875343276415727 (81 us) ln 2 = 0.6931471805599453094172321214581765680755001343602552541206 800094933936219696947156058633269964186875 (71 us) ln 10 = 2.3025850929940456840179914546843642076011014886287729760333 279009675726096773524802359972050895982983 (42 us) e = 2.7182818284590452353602874713526624977572470936999595749669 676277240766303535475945713821785251664274 (15 us) gamma = 0.5772156649015328606065120900824024310421593359399235988057 672348848677271199468391476858899497227991 (206 us) G = 0.9159655941772190150546035149323841107741493742816721342664 981196217630197762547694793565129261151062 (187 us) pi = 3.1415926535897932384626433832795028841971693993751058209749 445923078164062862089986280348253421170680 (601 us)
The timings above are all for first-time computations. Each constant is cached in thread_local storage, so subsequent calls at the same precision return instantly. G is Catalan's constant $G = \sum_{k=0}^{\infty} \frac{(-1)^k}{(2k+1)^2}$.

Demo 2: Computing Massive Digits of Pi (Chudnovsky)

The Chudnovsky formula produces about 14 digits per term. Combined with binary splitting and NTT multiplication, it can compute massive numbers of digits of pi at high speed.

pi (1,000 digits, first 200 shown): 3.1415926535897932384626433832795028841971693993751058209749 445923078164062862089986280348253421170679821480865132823066 470938446095505822317253594081284811174502841027019385211055 5964462294895493038196 ... 1000 digits: 146 us 10000 digits: 1 ms 100000 digits: 21 ms 1000000 digits: 419 ms
One million digits in just 0.4 seconds. Thanks to the combination of Chudnovsky + binary splitting + NTT multiplication, a 10x increase in digits only results in roughly a 20x increase in computation time.

Demo 3: Ramanujan's "Almost Integer"

$e^{\pi\sqrt{163}}$ is extremely close to an integer — the difference is merely $7.5 \times 10^{-13}$. This remarkable phenomenon occurs for special integers known as Heegner numbers, and is explained by the theory of j-invariants and complex multiplication (CM).

High-precision computation of e^(pi * sqrt(163)): pi = 3.141592653589793238462643383280... sqrt(163)= 12.767145334803704661710952009781... e^(pi * sqrt(163)) = 262537412640768743.9999999999992500725971981856888793538563 Similar phenomena with other Heegner numbers: e^(pi*sqrt(19 )) = 885479.77768015431949753789 e^(pi*sqrt(43 )) = 884736743.99977746603490666194 e^(pi*sqrt(67 )) = 147197952743.99999866245422450683 e^(pi*sqrt(163)) = 262537412640768743.99999999999925007260 The larger d is, the closer the result is to an integer.

$$e^{\pi\sqrt{163}} \approx 262537412640768744 - 7.5 \times 10^{-13}$$

Heegner numbers correspond to discriminants of imaginary quadratic fields $\mathbb{Q}(\sqrt{-d})$ with class number 1. There are only 9 such values: $d = 1, 2, 3, 7, 11, 19, 43, 67, 163$ (Stark-Heegner theorem).

Demo 4: Breaking the Floating-Point Precision Barrier

double provides only about 15 significant digits. We demonstrate the precision of Float through 3 examples: catastrophic cancellation, truncation error, and identity verification.

Catastrophic cancellation

--- Catastrophic cancellation --- x = 1e-15 (1 + x) - 1: double: 1.1102230246251565404e-15 Float: 0.000000000000001000000000000000 exact: 0.000000000000001

Basel problem

$$\sum_{k=1}^{\infty} \frac{1}{k^2} = \frac{\pi^2}{6}$$ This famous problem was solved by Euler in 1735. Convergence from finite sums is slow.

--- Basel problem: sum(1/k^2) -> pi^2/6 --- pi^2 / 6 (exact) = 1.644934066848226436472415166646 sum(1/k^2, k=1..100000) [double] = 1.64492406689824 sum(1/k^2, k=1..1000) [Float] = 1.643934566681559803139058023822 (The finite sum is less than the exact value — illustrating the slow convergence)

Identity verification: $\exp(\log(x)) = x$

--- Identity verification: exp(log(x)) = x --- x = 3.14159265358979323846264338327950288419716939937510 exp(log(x)) = 3.14159265358979323846264338327950288419716939937510 |x - exp(log(x))| = 0.00000000000000000000000000000000000000000000000000 (Computed at 50-digit precision, so the internal error is below 10^-50)
With double, $(1 + 10^{-15}) - 1$ incurs an 11% error. Float returns accurate values at any desired precision.

Demo 5: Independent Verification of Pi via Machin-Type Formulas

We compute pi using two different arctan formulas and confirm that they match the Chudnovsky result to 100 digits. When independent algorithms produce the same value, it serves as strong evidence of implementation correctness.

Machin (1706): $\dfrac{\pi}{4} = 4\arctan\dfrac{1}{5} - \arctan\dfrac{1}{239}$

Machin's formula: pi/4 = 4*arctan(1/5) - arctan(1/239) Machin: 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170680 Chudnovsky: 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170680 Difference: 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 (Machin: 41 us, Chudnovsky: 28 us)

Størmer: $\dfrac{\pi}{4} = 6\arctan\dfrac{1}{8} + 2\arctan\dfrac{1}{57} + \arctan\dfrac{1}{239}$

Stormer's formula: pi/4 = 6*atan(1/8) + 2*atan(1/57) + atan(1/239) Stormer: 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170680 Chudnovsky: 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170680 Difference: 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 (Stormer: 51 us)
Three completely independent algorithms (Chudnovsky, Machin, Størmer) agree to 100 digits. This is strong evidence that calx's exp, log, atan, and pi all work correctly.

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_float_demo.cpp (click to expand)
// Copyright (C) 2026 Kiyotsugu Arai
// SPDX-License-Identifier: LGPL-3.0-or-later

// example_float_demo.cpp
// Arbitrary-precision floating-point demo: 5 scenarios showcasing Float

#include <math/core/mp/Float.hpp>
#include <math/core/mp/Float/FloatMath.hpp>
#include <iostream>
#include <iomanip>
#include <string>
#include <chrono>
#include <cmath>

using namespace calx;

// ============================================================================
// Demo 1: Mathematical Constants — 100 Digits
//   — Compute pi, e, gamma, log2, sqrt2, Catalan to high precision
// ============================================================================
static void demo_constants() {
    std::cout << "============================================================\n";
    std::cout << " Demo 1: Mathematical Constants — 100 Digits\n";
    std::cout << "============================================================\n\n";

    int prec = 110;  // compute with extra margin
    int show = 100;

    struct {
        const char* name;
        const char* symbol;
        Float (*func)(int);
    } constants[] = {
        {"pi",      "pi",       Float::pi},
        {"e",       "e",        Float::e},
        {"gamma",   "gamma",    Float::euler},
        {"log 2",   "ln 2",     Float::log2},
        {"log 10",  "ln 10",    Float::log10},
        {"sqrt 2",  "sqrt(2)",  Float::sqrt2},
        {"catalan", "G",        Float::catalan},
    };

    for (auto& [name, symbol, func] : constants) {
        auto start = std::chrono::high_resolution_clock::now();
        Float val = func(prec);
        auto end = std::chrono::high_resolution_clock::now();
        auto us = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();

        std::string s = val.toDecimalString(show);
        // line break every 60 chars
        std::cout << "  " << std::setw(8) << std::left << symbol << " = ";
        for (size_t i = 0; i < s.size(); i += 60) {
            if (i > 0) std::cout << std::string(13, ' ');
            size_t len = std::min<size_t>(60, s.size() - i);
            std::cout << s.substr(i, len) << "\n";
        }
        std::cout << std::string(13, ' ') << "(" << us << " us)\n\n";
    }
}

// ============================================================================
// Demo 2: Pi — 1,000 and 10,000 Digits (Chudnovsky)
//   — Showcasing computation speed
// ============================================================================
static void demo_pi_digits() {
    std::cout << "============================================================\n";
    std::cout << " Demo 2: Pi — 1,000 and 10,000 Digits (Chudnovsky)\n";
    std::cout << "============================================================\n\n";

    // Compare computation speed at different digit counts
    int benchmarks[] = {1000, 10000, 100000, 1000000};
    for (int digits : benchmarks) {
        auto start = std::chrono::high_resolution_clock::now();
        Float pi = Float::pi(digits);
        auto end = std::chrono::high_resolution_clock::now();
        auto ms = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();

        if (digits == 1000) {
            // Show first 200 digits for the 1,000-digit case
            std::string s = pi.toDecimalString(200);
            std::cout << "  pi (1,000 digits, first 200 shown):\n\n";
            for (size_t i = 0; i < s.size(); i += 60) {
                size_t len = std::min<size_t>(60, s.size() - i);
                std::cout << "    " << s.substr(i, len) << "\n";
            }
            std::cout << "    ...\n";
        }

        if (ms >= 1000) {
            std::cout << "  " << std::setw(10) << digits << " digits: "
                      << (ms / 1000) << " ms\n";
        } else {
            std::cout << "  " << std::setw(10) << digits << " digits: "
                      << ms << " us\n";
        }
    }

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

// ============================================================================
// Demo 3: Ramanujan's "Almost Integer"
//   — e^(pi*sqrt(163)) is extremely close to an integer
// ============================================================================
static void demo_ramanujan() {
    std::cout << "============================================================\n";
    std::cout << " Demo 3: Ramanujan's \"Almost Integer\"\n";
    std::cout << "============================================================\n\n";

    int prec = 60;

    // e^(pi*sqrt(163)) ~ 262537412640768744 (extremely close to an integer)
    Float pi_val = Float::pi(prec);
    Float sqrt163 = sqrt(Float(163), prec);
    Float result = exp(pi_val * sqrt163, prec);

    std::string s = result.toDecimalString(40);

    std::cout << "  High-precision computation of e^(pi * sqrt(163)):\n\n";
    std::cout << "    pi       = " << pi_val.toDecimalString(30) << "...\n";
    std::cout << "    sqrt(163)= " << sqrt163.toDecimalString(30) << "...\n";
    std::cout << "\n    e^(pi * sqrt(163)) =\n";
    std::cout << "      " << s << "\n";

    std::cout << "\n  The fractional part is 0.9999999999...75,\n";
    std::cout << "  differing from the integer 262537412640768744 by about 7.5 * 10^-13.\n";
    std::cout << "  This remarkable phenomenon is explained by j-invariants and CM theory.\n";

    // Other "almost integers"
    std::cout << "\n  Similar phenomena with other Heegner numbers:\n";
    for (int d : {19, 43, 67, 163}) {
        Float sq = sqrt(Float(d), prec);
        Float val = exp(pi_val * sq, prec);
        std::string vs = val.toDecimalString(20);
        std::cout << "    e^(pi*sqrt(" << std::setw(3) << d << ")) = " << vs << "\n";
    }

    std::cout << "\n  The larger d is, the closer the result is to an integer.\n";
    std::cout << std::endl;
}

// ============================================================================
// Demo 4: Breaking the Precision Barrier
//   — Accurate computation where double breaks down
// ============================================================================
static void demo_precision_limit() {
    std::cout << "============================================================\n";
    std::cout << " Demo 4: Breaking the Precision Barrier\n";
    std::cout << "============================================================\n\n";

    // Catastrophic cancellation: (1 + x) - 1  (x extremely small)
    std::cout << "  --- Catastrophic cancellation ---\n\n";
    {
        // x = 1e-15
        double xd = 1e-15;
        double rd = (1.0 + xd) - 1.0;

        int prec = 60;
        Float one(1);
        one.setPrecision(prec);
        Float xf = pow(Float(10), -15, prec);
        Float rf = (one + xf) - one;

        std::cout << "    x = 1e-15\n";
        std::cout << "    (1 + x) - 1:\n";
        std::cout << std::setprecision(20);
        std::cout << "      double: " << rd << "\n";
        std::cout << "      Float:  " << rf.toDecimalString(30) << "\n";
        std::cout << "      exact:  0.000000000000001\n\n";
    }

    // Basel problem: sum(1/k^2, k=1..N) -> pi^2/6
    std::cout << "  --- Basel problem: sum(1/k^2) -> pi^2/6 ---\n\n";
    {
        int prec = 50;
        Float pi_val = Float::pi(prec);
        Float exact = sqr(pi_val, prec) / Float(6);

        // double: N=100000
        double sum_d = 0.0;
        for (int k = 1; k <= 100000; ++k)
            sum_d += 1.0 / (static_cast<double>(k) * k);

        // Float: N=1000
        Float sum_f = Float::zero();
        for (int k = 1; k <= 1000; ++k) {
            Float kf(k);
            sum_f = sum_f + Float(1) / (kf * kf);
        }

        std::cout << "    pi^2 / 6 (exact) = " << exact.toDecimalString(30) << "\n";
        std::cout << std::setprecision(15);
        std::cout << "    sum(1/k^2, k=1..100000) [double] = " << sum_d << "\n";
        std::cout << "    sum(1/k^2, k=1..1000)   [Float]  = " << sum_f.toDecimalString(30) << "\n";
        std::cout << "    (The finite sum is less than the exact value — illustrating the slow convergence)\n";
    }

    // Identity verification: e^(ln x) = x
    std::cout << "\n  --- Identity verification: exp(log(x)) = x ---\n\n";
    {
        int prec = 100;
        Float x("3.14159265358979323846264338327950288419716939937510");
        x.setPrecision(prec);

        Float lx = log(x, prec);
        Float elx = exp(lx, prec);

        std::cout << "    x              = " << x.toDecimalString(50) << "\n";
        std::cout << "    exp(log(x))    = " << elx.toDecimalString(50) << "\n";

        Float diff = abs(x - elx);
        std::cout << "    |x - exp(log(x))| = " << diff.toDecimalString(50) << "\n";
        std::cout << "    (Computed at 50-digit precision, so the internal error is below 10^-50)\n";
    }

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

// ============================================================================
// Demo 5: Machin's Formula — Independent Verification of Pi
//   — pi/4 = 4*arctan(1/5) - arctan(1/239)
// ============================================================================
static void demo_machin() {
    std::cout << "============================================================\n";
    std::cout << " Demo 5: Machin's Formula — Independent Verification of Pi\n";
    std::cout << "============================================================\n\n";

    int prec = 150;
    int show = 100;

    // Set precision before division (default precision would lose digits)
    auto makeRecip = [&](int d) {
        Float num(1); num.setPrecision(prec);
        Float den(d); den.setPrecision(prec);
        return num / den;
    };

    // Machin's formula: pi/4 = 4*arctan(1/5) - arctan(1/239)
    std::cout << "  Machin's formula: pi/4 = 4*arctan(1/5) - arctan(1/239)\n\n";
    {
        Float one_5th = makeRecip(5);
        Float one_239th = makeRecip(239);

        auto start = std::chrono::high_resolution_clock::now();
        Float atan_1_5 = atan(one_5th, prec);
        Float atan_1_239 = atan(one_239th, prec);
        Float pi_machin = (atan_1_5 * Float(4) - atan_1_239) * Float(4);
        auto end = std::chrono::high_resolution_clock::now();
        auto us = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();

        auto start2 = std::chrono::high_resolution_clock::now();
        Float pi_chud = Float::pi(prec + 10);  // avoid cache hit
        auto end2 = std::chrono::high_resolution_clock::now();
        auto us2 = std::chrono::duration_cast<std::chrono::microseconds>(end2 - start2).count();

        std::cout << "    Machin:     " << pi_machin.toDecimalString(show) << "\n";
        std::cout << "    Chudnovsky: " << pi_chud.toDecimalString(show) << "\n";

        Float diff = abs(pi_machin - pi_chud);
        std::cout << "\n    Difference: " << diff.toDecimalString(show) << "\n";
        std::cout << "    (Machin: " << us << " us, Chudnovsky: " << us2 << " us)\n";
    }

    // Stormer's formula: pi/4 = 6*arctan(1/8) + 2*arctan(1/57) + arctan(1/239)
    std::cout << "\n  Stormer's formula: pi/4 = 6*atan(1/8) + 2*atan(1/57) + atan(1/239)\n\n";
    {
        Float a8 = atan(makeRecip(8), prec);
        Float a57 = atan(makeRecip(57), prec);
        Float a239 = atan(makeRecip(239), prec);

        Float pi_stormer = (a8 * Float(6) + a57 * Float(2) + a239) * Float(4);
        Float pi_chud = Float::pi(prec);

        std::cout << "    Stormer:    " << pi_stormer.toDecimalString(show) << "\n";
        std::cout << "    Chudnovsky: " << pi_chud.toDecimalString(show) << "\n";

        Float diff = abs(pi_stormer - pi_chud);
        std::cout << "\n    Difference: " << diff.toDecimalString(show) << "\n";
    }

    std::cout << "\n  The fact that different algorithms produce the same result is\n";
    std::cout << "  strong evidence of the correctness of calx's arbitrary-precision operations (exp, log, atan).\n";
    std::cout << std::endl;
}

// ============================================================================
// main
// ============================================================================
int main() {
    std::cout << "###########################################################\n";
    std::cout << "#  calx Float Demo — The Power of Arbitrary-Precision FP   #\n";
    std::cout << "###########################################################\n\n";

    demo_constants();
    demo_pi_digits();
    demo_ramanujan();
    demo_precision_limit();
    demo_machin();

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

    return 0;
}

For API details, see the Float 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-float-demo
examples\Release\example-float-demo.exe

Benchmark Environment

The timings on this page are reference values measured in the following environment.

  • CPU: AMD Ryzen Threadripper PRO 5995WX (Zen 3)
  • OS: Windows 11 Pro
  • Compiler: MSVC 17.x (Visual Studio 2022), Release x64