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.
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.
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).
$$e^{\pi\sqrt{163}} \approx 262537412640768744 - 7.5 \times 10^{-13}$$
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
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.
Identity verification: $\exp(\log(x)) = x$
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}$
Størmer: $\dfrac{\pi}{4} = 6\arctan\dfrac{1}{8} + 2\arctan\dfrac{1}{57} + \arctan\dfrac{1}{239}$
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