LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
number.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_NUM_NUMBER_H
6#define LINE_NUM_NUMBER_H
7
8/**
9 * @file
10 * @ingroup line_num
11 * Number-type abstraction for the templated API port.
12 *
13 * Three arithmetic modes are exposed to callers:
14 * double - IEEE 754, what MATLAB / the JAR / native Python use today
15 * exact - arbitrary-precision rationals, no rounding at all
16 * real - fixed high-precision binary floating point
17 *
18 * The default backends are header-only Boost.Multiprecision types
19 * (Boost Software License 1.0), so the default build carries no LGPL
20 * obligation and a redistributable Python wheel remains possible. Defining
21 * LINE_MP_USE_GMP switches the exact and real backends to GMP mpq and MPFR,
22 * which are faster but LGPL; the algorithms are unchanged, only the typedef.
23 *
24 * Every algorithm is a template on the number type T and consults
25 * num_traits<T> for what T can do. Algorithms that need log/exp/pow assert
26 * num_traits<T>::has_transcendental at compile time, so instantiating an
27 * inherently inexact algorithm at exact arithmetic is a build error rather
28 * than a silent fallback.
29 */
30
31#include <cmath>
32#include <string>
33
34#include <boost/multiprecision/cpp_bin_float.hpp>
35#include <boost/multiprecision/cpp_int.hpp>
36
37#ifdef LINE_MP_USE_GMP
38#include <boost/multiprecision/gmp.hpp>
39#include <boost/multiprecision/mpfr.hpp>
40#endif
41
42namespace line {
43
44// Backend typedefs
45
46/* `Rational` IS PINNED TO et_off ON PURPOSE, and the `et_off` is the whole
47 * point of spelling these out rather than using Boost's `cpp_rational` /
48 * `mpq_rational` aliases, which are `et_on`.
49 *
50 * With expression templates on, `a + b` does not evaluate: it builds an
51 * `expression<...>` node holding REFERENCES to its operands. That is a
52 * use-after-free the moment the node outlives them, and a DEDUCED return type
53 * is how it escapes -- `[](Rational x) { return Rational(4) * x - Rational(1); }`
54 * returns a node over two temporaries that die with the return statement.
55 * It cost two SIGSEGVs (test_pfqn_mvac_oi_clw.cpp 2026-08-25,
56 * test_rootfind.cpp 2026-08-26), and both were invisible on the 22.04
57 * development host: Boost 1.72 added rvalue-reference handling to the
58 * operators, so 1.74 collapses that expression eagerly while 1.71 (every 20.04
59 * worker) does not. doctest cannot catch a SIGSEGV, so each one aborted the
60 * binary mid-run and left several hundred cases with no verdict at all.
61 *
62 * Turning the templates off removes the entire class rather than the two sites
63 * that happened to be found. It cannot change a single digit: rational
64 * arithmetic here is exact, so expression templates were only ever eliding
65 * temporaries, never altering a value. Do NOT "restore" the Boost aliases.
66 *
67 * BigInt and Real are left alone: `Real` is already et_off for these backends,
68 * and no BigInt expression is returned through a deduced type. */
69#ifdef LINE_MP_USE_GMP
70using Rational =
71 boost::multiprecision::number<boost::multiprecision::gmp_rational, boost::multiprecision::et_off>;
72using BigInt = boost::multiprecision::mpz_int;
73template <unsigned Digits10>
74using Real = boost::multiprecision::number<boost::multiprecision::mpfr_float_backend<Digits10>>;
75#else
76using Rational = boost::multiprecision::number<boost::multiprecision::cpp_rational_backend,
77 boost::multiprecision::et_off>;
78using BigInt = boost::multiprecision::cpp_int;
79template <unsigned Digits10>
80using Real = boost::multiprecision::number<boost::multiprecision::cpp_bin_float<Digits10>>;
81#endif
82
83/** Precision tiers offered by the CLI's --arith real:`<digits>` flag. */
87
88// ---------------------------------------------------------------------------
89// log of a big integer, without ever forming the value as a double
90// ---------------------------------------------------------------------------
91
92/**
93 * log(v) for a positive arbitrary-precision integer. Keeps only the leading
94 * 53 bits of the mantissa and accounts for the discarded bits in the exponent,
95 * so the result is finite for values far outside the double range.
96 */
97inline double log_bigint(const BigInt& v) {
98 if (v == 0) return -std::numeric_limits<double>::infinity();
99 BigInt a = v < 0 ? BigInt(-v) : v;
100 long bits = static_cast<long>(boost::multiprecision::msb(a)) + 1;
101 long shift = bits > 53 ? bits - 53 : 0;
102 BigInt mant = a >> shift;
103 return std::log(static_cast<double>(mant)) + static_cast<double>(shift) * std::log(2.0);
104}
105
106// ---------------------------------------------------------------------------
107// num_traits
108// ---------------------------------------------------------------------------
109
110template <class T>
111struct num_traits; // intentionally undefined for unsupported types
112
113template <>
114struct num_traits<double> {
115 using type = double;
116 static constexpr bool is_exact = false;
117 static constexpr bool has_transcendental = true;
118 static const char* name() { return "double"; }
119
120 static double from_int(long v) { return static_cast<double>(v); }
121 static double from_rational(long num, long den) {
122 return static_cast<double>(num) / static_cast<double>(den);
123 }
124 static double from_double(double v) { return v; }
125 static double to_double(const double& v) { return v; }
126 /** log of the value, always returned as a double. */
127 static double log_as_double(const double& v) { return std::log(v); }
128 static std::string to_string(const double& v) { return std::to_string(v); }
129};
130
131template <>
133 using type = Rational;
134 static constexpr bool is_exact = true;
135 static constexpr bool has_transcendental = false;
136 static const char* name() { return "exact"; }
137
138 static Rational from_int(long v) { return Rational(v); }
139 static Rational from_rational(long num, long den) { return Rational(num, den); }
140 /** Exact: a double is a dyadic rational, so this conversion loses nothing. */
141 static Rational from_double(double v) { return Rational(v); }
142 static double to_double(const Rational& v) { return static_cast<double>(v); }
143 static double log_as_double(const Rational& v) {
144 return log_bigint(BigInt(numerator(v))) - log_bigint(BigInt(denominator(v)));
145 }
146 static std::string to_string(const Rational& v) { return v.str(); }
147 static std::string numerator_str(const Rational& v) { return BigInt(numerator(v)).str(); }
148 static std::string denominator_str(const Rational& v) { return BigInt(denominator(v)).str(); }
149};
150
151template <unsigned D>
152struct num_traits<Real<D>> {
153 using type = Real<D>;
154 static constexpr bool is_exact = false;
155 static constexpr bool has_transcendental = true;
156 static constexpr unsigned digits10 = D;
157 static const char* name() { return "real"; }
158
159 static Real<D> from_int(long v) { return Real<D>(v); }
160 static Real<D> from_rational(long num, long den) { return Real<D>(num) / Real<D>(den); }
161 static Real<D> from_double(double v) { return Real<D>(v); }
162 static double to_double(const Real<D>& v) { return static_cast<double>(v); }
163 static double log_as_double(const Real<D>& v) { return static_cast<double>(log(v)); }
164 static std::string to_string(const Real<D>& v) { return v.str(); }
165};
166
167// ---------------------------------------------------------------------------
168// Generic helpers usable from any algorithm
169// ---------------------------------------------------------------------------
170
171template <class T>
172inline T num_abs(const T& v) {
173 using std::abs;
174 return abs(v);
175}
176
177template <>
178inline double num_abs<double>(const double& v) {
179 return std::fabs(v);
180}
181
182/** Factorial as a value of T. Exact for Rational and for BigInt-backed types. */
183template <class T>
184inline T num_factorial(unsigned n) {
186 for (unsigned k = 2; k <= n; ++k) f *= num_traits<T>::from_int(static_cast<long>(k));
187 return f;
188}
189
190/** Integer power, valid in any field (no transcendental requirement). */
191template <class T>
192inline T num_pow_int(const T& base, unsigned e) {
194 T b = base;
195 unsigned k = e;
196 while (k > 0) {
197 if (k & 1u) r *= b;
198 b *= b;
199 k >>= 1;
200 }
201 return r;
202}
203
204} // namespace line
205
206#endif // LINE_NUM_NUMBER_H
T num_factorial(unsigned n)
Factorial as a value of T.
Definition number.h:184
T num_abs(const T &v)
Definition number.h:172
Real< 50 > Real50
Precision tiers offered by the CLI's –arith real:<digits> flag.
Definition number.h:84
T num_pow_int(const T &base, unsigned e)
Integer power, valid in any field (no transcendental requirement).
Definition number.h:192
boost::multiprecision::cpp_int BigInt
Definition number.h:78
Real< 200 > Real200
Definition number.h:86
boost::multiprecision::number< boost::multiprecision::cpp_rational_backend, boost::multiprecision::et_off > Rational
Definition number.h:76
boost::multiprecision::number< boost::multiprecision::cpp_bin_float< Digits10 > > Real
Definition number.h:80
Real< 100 > Real100
Definition number.h:85
double log_bigint(const BigInt &v)
log(v) for a positive arbitrary-precision integer.
Definition number.h:97
static const char * name()
Definition number.h:136
static std::string numerator_str(const Rational &v)
Definition number.h:147
static Rational from_int(long v)
Definition number.h:138
static constexpr bool has_transcendental
Definition number.h:135
static std::string to_string(const Rational &v)
Definition number.h:146
static double to_double(const Rational &v)
Definition number.h:142
static Rational from_rational(long num, long den)
Definition number.h:139
static Rational from_double(double v)
Exact: a double is a dyadic rational, so this conversion loses nothing.
Definition number.h:141
static std::string denominator_str(const Rational &v)
Definition number.h:148
static double log_as_double(const Rational &v)
Definition number.h:143
static constexpr bool is_exact
Definition number.h:134
static std::string to_string(const Real< D > &v)
Definition number.h:164
static const char * name()
Definition number.h:157
static constexpr unsigned digits10
Definition number.h:156
static Real< D > from_int(long v)
Definition number.h:159
static Real< D > from_rational(long num, long den)
Definition number.h:160
static Real< D > from_double(double v)
Definition number.h:161
static constexpr bool has_transcendental
Definition number.h:155
static double log_as_double(const Real< D > &v)
Definition number.h:163
static constexpr bool is_exact
Definition number.h:154
static double to_double(const Real< D > &v)
Definition number.h:162
static double from_double(double v)
Definition number.h:124
static std::string to_string(const double &v)
Definition number.h:128
static constexpr bool is_exact
Definition number.h:116
static double log_as_double(const double &v)
log of the value, always returned as a double.
Definition number.h:127
static const char * name()
Definition number.h:118
static double from_int(long v)
Definition number.h:120
static double from_rational(long num, long den)
Definition number.h:121
static constexpr bool has_transcendental
Definition number.h:117
static double to_double(const double &v)
Definition number.h:125