LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
decimal.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_UTIL_DECIMAL_H
6#define LINE_UTIL_DECIMAL_H
7
8/**
9 * @file
10 * @ingroup line_util
11 * Decimal literal -> T, without a detour through double when T is exact.
12 *
13 * This is the boundary at which a model file becomes numbers, and it is the
14 * one place where "exact arithmetic" has to decide what it is exact ABOUT. A
15 * host demand written `0.01` in an .lqnx file denotes the rational 1/100. The
16 * double nearest to it is 0.01000000000000000020816681711721685...; routing
17 * that literal through `num_traits<Rational>::from_double` would make the
18 * exact backend compute, with no rounding at all, the answer to a model that
19 * is not the one on disk. So the exact backend parses the decimal digits
20 * directly into num/10^k, and the inexact backends keep strtod, which is what
21 * MATLAB's str2double and Java's Double.parseDouble also do.
22 *
23 * The consequence to keep in mind when comparing the two runs: they are not
24 * two evaluations of one arithmetic problem, they are the exact solution of
25 * the declared model versus the floating-point solution of its double
26 * rounding. Their difference is bounded below by the input rounding, of order
27 * 1e-17 relative here, and any larger gap is accumulated error in the solver.
28 */
29
30#include <cctype>
31#include <cstdlib>
32#include <string>
33
34#include "line/num/number.h"
35#include "line/util/error.h"
36
37namespace line {
38
39namespace detail {
40
41/** Split a decimal literal into (sign, digits, exponent) with value = sign * digits * 10^exp. */
42inline bool decimal_parts(const std::string& s, bool& neg, std::string& digits, long& exp10) {
43 std::size_t i = 0;
44 while (i < s.size() && std::isspace(static_cast<unsigned char>(s[i]))) ++i;
45 neg = false;
46 if (i < s.size() && (s[i] == '+' || s[i] == '-')) neg = (s[i++] == '-');
47
48 digits.clear();
49 exp10 = 0;
50 bool any = false;
51 while (i < s.size() && std::isdigit(static_cast<unsigned char>(s[i]))) {
52 digits.push_back(s[i++]);
53 any = true;
54 }
55 if (i < s.size() && s[i] == '.') {
56 ++i;
57 while (i < s.size() && std::isdigit(static_cast<unsigned char>(s[i]))) {
58 digits.push_back(s[i++]);
59 --exp10;
60 any = true;
61 }
62 }
63 if (!any) return false;
64 if (i < s.size() && (s[i] == 'e' || s[i] == 'E')) {
65 ++i;
66 bool eneg = false;
67 if (i < s.size() && (s[i] == '+' || s[i] == '-')) eneg = (s[i++] == '-');
68 long e = 0;
69 bool anye = false;
70 while (i < s.size() && std::isdigit(static_cast<unsigned char>(s[i]))) {
71 e = e * 10 + (s[i++] - '0');
72 anye = true;
73 }
74 if (!anye) return false;
75 exp10 += eneg ? -e : e;
76 }
77 while (i < s.size() && std::isspace(static_cast<unsigned char>(s[i]))) ++i;
78 return i == s.size();
79}
80
81} // namespace detail
82
83/** The literal as an exact rational, num/10^k with no rounding. */
84inline Rational rational_from_decimal(const std::string& s) {
85 bool neg = false;
86 std::string digits;
87 long exp10 = 0;
88 if (!detail::decimal_parts(s, neg, digits, exp10))
89 throw InputError("num_from_decimal: '" + s + "' is not a decimal literal");
90 BigInt mant = 0;
91 for (char c : digits) mant = mant * 10 + (c - '0');
92 BigInt num = mant;
93 BigInt den = 1;
94 if (exp10 >= 0) {
95 for (long k = 0; k < exp10; ++k) num *= 10;
96 } else {
97 for (long k = 0; k < -exp10; ++k) den *= 10;
98 }
99 Rational r(num, den);
100 return neg ? Rational(-r) : r;
101}
102
103/**
104 * Parse a decimal literal into T.
105 *
106 * Rational reconstructs num/10^k from the digits; every other backend uses
107 * strtod, matching what MATLAB's str2double and Java's Double.parseDouble do.
108 */
109template <class T>
110T num_from_decimal(const std::string& s) {
111 return num_traits<T>::from_double(std::strtod(s.c_str(), nullptr));
112}
113
114template <>
115inline Rational num_from_decimal<Rational>(const std::string& s) {
116 return rational_from_decimal(s);
117}
118
119/** Parse a decimal literal as a plain double (multiplicities, populations, tolerances). */
120inline double dbl_from_decimal(const std::string& s, double fallback) {
121 if (s.empty()) return fallback;
122 const char* p = s.c_str();
123 char* end = nullptr;
124 const double v = std::strtod(p, &end);
125 if (end == p) return fallback;
126 return v;
127}
128
129} // namespace line
130
131#endif // LINE_UTIL_DECIMAL_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
double dbl_from_decimal(const std::string &s, double fallback)
Parse a decimal literal as a plain double (multiplicities, populations, tolerances).
Definition decimal.h:120
boost::multiprecision::cpp_int BigInt
Definition number.h:78
T num_from_decimal(const std::string &s)
Parse a decimal literal into T.
Definition decimal.h:110
Rational rational_from_decimal(const std::string &s)
The literal as an exact rational, num/10^k with no rounding.
Definition decimal.h:84
boost::multiprecision::number< boost::multiprecision::cpp_rational_backend, boost::multiprecision::et_off > Rational
Definition number.h:76
Number-type abstraction for the templated API port.