HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
decimal.hpp
1// Copyright 2019 Pokitec
2// All rights reserved.
3
4#pragma once
5
6#include "TTauri/Foundation/throw_exception.hpp"
7#include "TTauri/Foundation/int_overflow.hpp"
8#include "TTauri/Foundation/math.hpp"
9#include <fmt/ostream.h>
10#include <limits>
11#include <string_view>
12#include <string>
13#include <charconv>
14#include <ostream>
15
16namespace tt {
17
18class decimal {
19private:
20 /* Value contains an 8 bit signed exponent in the most-significant bits
21 * and a 56 bit signed mantissa in the least-significant bits.
22 */
23 uint64_t value;
24
25public:
26 constexpr static int mantissa_bits = 56;
27 constexpr static int exponent_bits = 8;
28 constexpr static int exponent_max = 127;
29 constexpr static int exponent_min = -128;
30
31 constexpr decimal() noexcept : value(0) {}
32 constexpr decimal(decimal const &other) noexcept = default;
33 constexpr decimal(decimal &&other) noexcept = default;
34 constexpr decimal &operator=(decimal const &other) noexcept = default;
35 constexpr decimal &operator=(decimal &&other) noexcept = default;
36
37 constexpr decimal(int exponent, long long mantissa) noexcept :
38 value(decimal::pack(exponent, mantissa)) {}
39
40 constexpr decimal(std::pair<int,long long> exponent_mantissa) :
41 decimal(exponent_mantissa.first, exponent_mantissa.second) {}
42
43 decimal(std::string_view str) : decimal(to_exponent_mantissa(str)) {}
44 decimal(double x) noexcept : decimal(to_exponent_mantissa(x)) {}
45 decimal(float x) noexcept : decimal(to_exponent_mantissa(x)) {}
46 constexpr decimal(signed long long x) : decimal(0, x) {}
47 constexpr decimal(signed long x) : decimal(0, static_cast<signed long long>(x)) {}
48 constexpr decimal(signed int x) : decimal(0, static_cast<signed long long>(x)) {}
49 constexpr decimal(signed short x) : decimal(0, static_cast<signed long long>(x)) {}
50 constexpr decimal(signed char x) : decimal(0, static_cast<signed long long>(x)) {}
51 constexpr decimal(unsigned long long x) : decimal(0, x) {}
52 constexpr decimal(unsigned long x) : decimal(0, static_cast<signed long long>(x)) {}
53 constexpr decimal(unsigned int x) : decimal(0, static_cast<signed long long>(x)) {}
54 constexpr decimal(unsigned short x) : decimal(0, static_cast<signed long long>(x)) {}
55 constexpr decimal(unsigned char x) : decimal(0, static_cast<signed long long>(x)) {}
56
57 constexpr decimal &operator=(std::pair<int,long long> other) noexcept { value = decimal::pack(other.first, other.second); return *this; }
58 decimal &operator=(std::string_view str) noexcept { return *this = to_exponent_mantissa(str); }
59 constexpr decimal &operator=(double other) noexcept { return *this = to_exponent_mantissa(other); }
60 constexpr decimal &operator=(float other) noexcept { return *this = to_exponent_mantissa(other); }
61 constexpr decimal &operator=(signed long long other) noexcept { value = decimal::pack(0, other); return *this; }
62 constexpr decimal &operator=(signed long other) noexcept { return *this = static_cast<signed long long>(other); }
63 constexpr decimal &operator=(signed int other) noexcept { return *this = static_cast<signed long long>(other); }
64 constexpr decimal &operator=(signed short other) noexcept { return *this = static_cast<signed long long>(other); }
65 constexpr decimal &operator=(signed char other) noexcept { return *this = static_cast<signed long long>(other); }
66 constexpr decimal &operator=(unsigned long long other) noexcept { return *this = static_cast<signed long long>(other); }
67 constexpr decimal &operator=(unsigned long other) noexcept { return *this = static_cast<signed long long>(other); }
68 constexpr decimal &operator=(unsigned int other) noexcept { return *this = static_cast<signed long long>(other); }
69 constexpr decimal &operator=(unsigned short other) noexcept { return *this = static_cast<signed long long>(other); }
70 constexpr decimal &operator=(unsigned char other) noexcept { return *this = static_cast<signed long long>(other); }
71
72 explicit operator signed long long () {
73 auto e = exponent();
74 auto m = mantissa();
75
76 while (e < 0) {
77 m /= 10;
78 e++;
79 }
80
81 while (e > 0) {
82 m *= 10;
83 e--;
84 if (!is_valid_mantissa(m)) {
86 }
87 }
88 return m;
89 }
90
91 explicit operator signed long () { return numeric_cast<signed long>(static_cast<signed long long>(*this)); }
92 explicit operator signed int () { return numeric_cast<signed int>(static_cast<signed long long>(*this)); }
93 explicit operator signed short () { return numeric_cast<signed short>(static_cast<signed long long>(*this)); }
94 explicit operator signed char () { return numeric_cast<signed char>(static_cast<signed long long>(*this)); }
95 explicit operator unsigned long long () { return numeric_cast<unsigned int>(static_cast<signed long long>(*this)); }
96 explicit operator unsigned long () { return numeric_cast<unsigned long>(static_cast<signed long long>(*this)); }
97 explicit operator unsigned int () { return numeric_cast<unsigned int>(static_cast<signed long long>(*this)); }
98 explicit operator unsigned short () { return numeric_cast<unsigned short>(static_cast<signed long long>(*this)); }
99 explicit operator unsigned char () { return numeric_cast<unsigned char>(static_cast<signed long long>(*this)); }
100 explicit operator long double () { return static_cast<long double>(mantissa()) * powl(10.0, exponent()); }
101 explicit operator double () { return static_cast<double>(static_cast<long double>(*this)); }
102 explicit operator float () { return static_cast<float>(static_cast<long double>(*this)); }
103
104 size_t hash() const noexcept {
105 auto v = this->normalize();
106 return std::hash<uint64_t>{}(v.value);
107 }
108
113 [[nodiscard]] constexpr int exponent() const noexcept {
114 return static_cast<int8_t>(value);
115 }
116
121 [[nodiscard]] constexpr long long mantissa() const noexcept {
122 return static_cast<int64_t>(value) >> 8;
123 }
124
125 [[nodiscard]] constexpr std::pair<int,long long> exponent_mantissa() const noexcept {
126 return {exponent(), mantissa()};
127 }
128
134 [[nodiscard]] constexpr decimal normalize() const noexcept {
135 ttlet [e, m] = exponent_mantissa();
136 ttlet [e_, m_] = decimal::normalize(e, m);
137 return {e_, m_};
138 }
139
140 decimal &operator+=(decimal rhs) noexcept {
141 ttlet [e, lhs_m, rhs_m] = decimal::align(*this, rhs);
142 value = decimal::pack(e, lhs_m + rhs_m);
143 return *this;
144 }
145
146 decimal &operator-=(decimal rhs) noexcept {
147 ttlet [e, lhs_m, rhs_m] = decimal::align(*this, rhs);
148 value = decimal::pack(e, lhs_m - rhs_m);
149 return *this;
150 }
151
152 decimal &operator*=(decimal rhs) noexcept {
153 return *this = *this * rhs;
154 }
155
156 decimal &operator/=(decimal rhs) noexcept {
157 return *this = *this / rhs;
158 }
159
160public:
161 [[nodiscard]] friend bool operator==(decimal lhs, decimal rhs) noexcept {
162 ttlet [e, lhs_m, rhs_m] = decimal::align(lhs, rhs);
163 return lhs_m == rhs_m;
164 }
165
166 [[nodiscard]] friend bool operator<(decimal lhs, decimal rhs) noexcept {
167 ttlet [e, lhs_m, rhs_m] = decimal::align(lhs, rhs);
168 return lhs_m < rhs_m;
169 }
170
171 [[nodiscard]] friend bool operator!=(decimal lhs, decimal rhs) noexcept { return !(lhs == rhs); }
172 [[nodiscard]] friend bool operator>(decimal lhs, decimal rhs) noexcept { return rhs < lhs; }
173 [[nodiscard]] friend bool operator<=(decimal lhs, decimal rhs) noexcept { return !(lhs > rhs); }
174 [[nodiscard]] friend bool operator>=(decimal lhs, decimal rhs) noexcept { return !(lhs < rhs); }
175
176 [[nodiscard]] friend constexpr decimal operator-(decimal rhs) noexcept {
177 return {rhs.exponent(), -rhs.mantissa()};
178 }
179
180 [[nodiscard]] friend decimal operator+(decimal lhs, decimal rhs) noexcept {
181 ttlet [e, lhs_m, rhs_m] = decimal::align(lhs, rhs);
182 return {e, lhs_m + rhs_m};
183 }
184
185 [[nodiscard]] friend decimal operator-(decimal lhs, decimal rhs) noexcept {
186 ttlet [e, lhs_m, rhs_m] = decimal::align(lhs, rhs);
187 return {e, lhs_m - rhs_m};
188 }
189
190 [[nodiscard]] friend decimal operator*(decimal lhs, decimal rhs) noexcept {
191 auto lhs_e = lhs.exponent();
192 auto lhs_m = lhs.mantissa();
193 auto rhs_e = rhs.exponent();
194 auto rhs_m = rhs.mantissa();
195
196 long long m = 0;
197 if (tt_likely(!mul_overflow(lhs_m, rhs_m, &m))) {
198 return {lhs_e + rhs_e, m};
199 }
200
201 // Try with normalized decimals, this is without loss of precision.
202 std::tie(lhs_e, lhs_m) = decimal::normalize(lhs_e, lhs_m);
203 std::tie(rhs_e, rhs_m) = decimal::normalize(rhs_e, rhs_m);
204 while (mul_overflow(lhs_m, rhs_m, &m)) {
205 // Reduce precision of largest mantissa until multiplication succeeds.
206 if (lhs_m > rhs_m) {
207 lhs_m += 5;
208 lhs_m /= 10;
209 lhs_e++;
210 } else {
211 rhs_m += 5;
212 rhs_m /= 10;
213 rhs_e++;
214 }
215 }
216 return {lhs_e + rhs_e, m};
217 }
218
219 [[nodiscard]] friend decimal operator/(decimal lhs, decimal rhs) noexcept {
220 auto rhs_m = rhs.mantissa();
221 tt_assume(rhs_m != 0);
222 auto rhs_e = rhs.exponent();
223 auto lhs_m = lhs.mantissa();
224 auto lhs_e = lhs.exponent();
225
226 std::tie(lhs_e, lhs_m) = decimal::denormalize(lhs_e, lhs_m);
227 return { lhs_e - rhs_e, lhs_m / rhs_m };
228 }
229
230 [[nodiscard]] friend decimal operator%(decimal lhs, decimal rhs) noexcept {
231 auto rhs_m = rhs.mantissa();
232 tt_assume(rhs_m != 0);
233 auto rhs_e = rhs.exponent();
234 auto lhs_m = lhs.mantissa();
235 auto lhs_e = lhs.exponent();
236
237 std::tie(lhs_e, lhs_m) = decimal::denormalize(lhs_e, lhs_m);
238 return { lhs_e - rhs_e, lhs_m % rhs_m };
239 }
240
241 [[nodiscard]] friend std::string to_string(decimal x) noexcept {
242 auto [e, m] = x.exponent_mantissa();
243 auto s = std::to_string(std::abs(m));
244
245 auto decimal_position = -e;
246 auto leading_zeros = (decimal_position - ssize(s)) + 1;
247 if (leading_zeros > 0) {
248 s.insert(0, leading_zeros, '0');
249 }
250
251 auto trailing_zeros = e;
252 if (trailing_zeros > 0) {
253 s.append(trailing_zeros, '0');
254 }
255
256 if (decimal_position > 0) {
257 s.insert(s.size() - decimal_position, 1, '.');
258 }
259
260 if (m < 0) {
261 s.insert(0, 1, '-');
262 }
263
264 return s;
265 }
266
267 friend std::ostream& operator<<(std::ostream& lhs, decimal rhs) {
268 return lhs << to_string(rhs);
269 }
270
271
272private:
273
276 [[nodiscard]] constexpr static std::pair<int,long long> normalize(int e, long long m) noexcept {
277 if (m != 0) {
278 while (m % 10 == 0) {
279 m /= 10;
280 e++;
281 }
282 }
283 return {e, m};
284 }
285
288 [[nodiscard]] constexpr static std::pair<int,long long> denormalize(int e, long long m) noexcept {
289 if (m != 0) {
290 // The mantissa is allowed to go slightly over the maximum; since it
291 // is used mostly for the rhs of a division, which means the result will,
292 // in all probability, make the mantissa smaller than the maximum.
293 while (is_valid_mantissa(m)) {
294 m *= 10;
295 e--;
296 }
297 }
298 return {e, m};
299 }
300
304 [[nodiscard]] constexpr static bool is_valid_mantissa(long long m) noexcept {
305 m >>= (mantissa_bits - 1);
306 return m == 0 || m == -1;
307 }
308
312 [[nodiscard]] constexpr static bool is_valid_exponent(int e) noexcept {
313 e >>= (exponent_bits - 1);
314 return e == 0 || e == -1;
315 }
316
317
318 [[nodiscard]] static std::tuple<int,long long,long long> align(decimal lhs, decimal rhs) noexcept {
319 auto lhs_e = lhs.exponent();
320 auto lhs_m = lhs.mantissa();
321 auto rhs_e = rhs.exponent();
322 auto rhs_m = rhs.mantissa();
323
324 if (lhs_e == rhs_e) {
325 // No alignment needed.
326 } else if (lhs_e > rhs_e) {
327 do {
328 lhs_m *= 10;
329 lhs_e--;
330 if (!is_valid_mantissa(lhs_m)) {
331 while (lhs_e > rhs_e) {
332 rhs_m /= 10;
333 rhs_e++;
334 }
335 break;
336 }
337 } while (lhs_e > rhs_e);
338 } else {
339 do {
340 rhs_m *= 10;
341 rhs_e--;
342 if (!is_valid_mantissa(lhs_m)) {
343 while (lhs_e < rhs_e) {
344 lhs_m /= 10;
345 lhs_e++;
346 }
347 break;
348 }
349 } while (lhs_e < rhs_e);
350 }
351 return {lhs_e, lhs_m, rhs_m};
352 }
353
356 [[nodiscard]] constexpr static uint64_t pack(int e, long long m) noexcept {
357 // Adjust an mantissa that is too large. Precision may be lost.
358 while (tt_unlikely(!is_valid_mantissa(m))) {
359 m /= 10;
360 e++;
361 tt_assert(e <= exponent_max);
362 }
363
364 while (tt_unlikely(e > exponent_max)) {
365 if ((m *= 10) == 0) {
366 e = exponent_max;
367 break;
368 }
369 e--;
370
371 // abort on overflow. This decimal does not support infinite.
372 tt_assert(is_valid_mantissa(m));
373 }
374
375 while (tt_unlikely(e < exponent_min)) {
376 if ((m /= 10) == 0) {
377 e = exponent_min;
378 break;
379 }
380 e++;
381 }
382
383 return
384 static_cast<uint64_t>(m) << exponent_bits |
385 static_cast<uint8_t>(e);
386 }
387
388 [[nodiscard]] static std::pair<int,long long> to_exponent_mantissa(double x) noexcept {
389 uint64_t x_;
390 std::memcpy(&x_, &x, sizeof(x_));
391
392 auto e2 = static_cast<int>((x_ << 1) >> 53) - 1023 - 52;
393 auto m = static_cast<long long>((x_ << 12) >> 12);
394 if (e2 > (-1023 - 52)) {
395 // Add leading '1'.
396 m |= (1LL << 52);
397 }
398
399 if (static_cast<int64_t>(x_) < 0) {
400 m = -m;
401 }
402
403 if (m == 0) {
404 return {0, 0};
405 }
406
407 int e10 = 0;
408 while (e2 < 0) {
409 while (is_valid_mantissa(m)) {
410 m *= 10;
411 e10--;
412 }
413 m /= 2;
414 e2++;
415 }
416
417 while (e2 > 0) {
418 while (!is_valid_mantissa(m)) {
419 m /= 10;
420 e10++;
421 }
422 m *= 2;
423 e2--;
424 }
425
426 return {e10, m};
427 }
428
429 [[nodiscard]] std::pair<int,long long> to_exponent_mantissa(std::string_view str) {
430 std::string mantissa_str;
431
432 int nr_digits = 0;
433 int nr_digits_in_front_of_point = -1;
434 for (ttlet c: str) {
435 if (c >= '0' && c <= '9') {
436 mantissa_str += c;
437 nr_digits++;
438 } else if (c == '.') {
439 nr_digits_in_front_of_point = nr_digits;
440 } else if (c == '\'' || c == ',') {
441 // Ignore thousand separators.
442 } else if (c == '-') {
443 mantissa_str += c;
444 } else {
445 TTAURI_THROW_PARSE_ERROR("Unexpected character in decimal number '{}'", str);
446 }
447 }
448
449 int exponent = (nr_digits_in_front_of_point >= 0) ? (nr_digits_in_front_of_point - nr_digits) : 0;
450
451 auto first = mantissa_str.data();
452 auto last = first + mantissa_str.size();
453 long long mantissa;
454 auto result = std::from_chars(first, last, mantissa, 10);
455 if (result.ptr == first) {
456 TTAURI_THROW_PARSE_ERROR("Could not parse mantissa '{}'", mantissa_str);
457 } else if (result.ec == std::errc::result_out_of_range) {
458 TTAURI_THROW_PARSE_ERROR("Mantissa '{}' out of range ", mantissa_str);
459 } else {
460 return {exponent, mantissa};
461 }
462 }
463};
464
465}
466
467namespace std {
468
469template<>
470struct hash<tt::decimal> {
471 inline size_t operator()(tt::decimal const& value) const {
472 return value.hash();
473 }
474};
475
476}
STL namespace.
Definition decimal.hpp:18
constexpr decimal normalize() const noexcept
Return a normalized decimal.
Definition decimal.hpp:134
constexpr long long mantissa() const noexcept
Extract mantissa from value.
Definition decimal.hpp:121
constexpr int exponent() const noexcept
Extract exponent from value.
Definition decimal.hpp:113
T data(T... args)
T memcpy(T... args)
T operator()(T... args)
T size(T... args)
T terminate(T... args)
T tie(T... args)
T to_string(T... args)