HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
decimal.hpp
1// Copyright Take Vos 2019-2020.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "exception.hpp"
8#include "int_overflow.hpp"
9#include "math.hpp"
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 : value(decimal::pack(exponent, mantissa)) {}
38
39 constexpr decimal(std::pair<int, long long> exponent_mantissa) : decimal(exponent_mantissa.first, exponent_mantissa.second) {}
40
41 decimal(std::string_view str) : decimal(to_exponent_mantissa(str)) {}
42 decimal(double x) noexcept : decimal(to_exponent_mantissa(x)) {}
43 decimal(float x) noexcept : decimal(to_exponent_mantissa(x)) {}
44 constexpr decimal(signed long long x) : decimal(0, x) {}
45 constexpr decimal(signed long x) : decimal(0, static_cast<signed long long>(x)) {}
46 constexpr decimal(signed int x) : decimal(0, static_cast<signed long long>(x)) {}
47 constexpr decimal(signed short x) : decimal(0, static_cast<signed long long>(x)) {}
48 constexpr decimal(signed char x) : decimal(0, static_cast<signed long long>(x)) {}
49 constexpr decimal(unsigned long long x) : decimal(0, x) {}
50 constexpr decimal(unsigned long x) : decimal(0, static_cast<signed long long>(x)) {}
51 constexpr decimal(unsigned int x) : decimal(0, static_cast<signed long long>(x)) {}
52 constexpr decimal(unsigned short x) : decimal(0, static_cast<signed long long>(x)) {}
53 constexpr decimal(unsigned char x) : decimal(0, static_cast<signed long long>(x)) {}
54
55 constexpr decimal &operator=(std::pair<int, long long> other) noexcept
56 {
57 value = decimal::pack(other.first, other.second);
58 return *this;
59 }
60 decimal &operator=(std::string_view str) noexcept
61 {
62 return *this = to_exponent_mantissa(str);
63 }
64 constexpr decimal &operator=(double other) noexcept
65 {
66 return *this = to_exponent_mantissa(other);
67 }
68 constexpr decimal &operator=(float other) noexcept
69 {
70 return *this = to_exponent_mantissa(other);
71 }
72 constexpr decimal &operator=(signed long long other) noexcept
73 {
74 value = decimal::pack(0, other);
75 return *this;
76 }
77 constexpr decimal &operator=(signed long other) noexcept
78 {
79 return *this = static_cast<signed long long>(other);
80 }
81 constexpr decimal &operator=(signed int other) noexcept
82 {
83 return *this = static_cast<signed long long>(other);
84 }
85 constexpr decimal &operator=(signed short other) noexcept
86 {
87 return *this = static_cast<signed long long>(other);
88 }
89 constexpr decimal &operator=(signed char other) noexcept
90 {
91 return *this = static_cast<signed long long>(other);
92 }
93 constexpr decimal &operator=(unsigned long long other) noexcept
94 {
95 return *this = static_cast<signed long long>(other);
96 }
97 constexpr decimal &operator=(unsigned long other) noexcept
98 {
99 return *this = static_cast<signed long long>(other);
100 }
101 constexpr decimal &operator=(unsigned int other) noexcept
102 {
103 return *this = static_cast<signed long long>(other);
104 }
105 constexpr decimal &operator=(unsigned short other) noexcept
106 {
107 return *this = static_cast<signed long long>(other);
108 }
109 constexpr decimal &operator=(unsigned char other) noexcept
110 {
111 return *this = static_cast<signed long long>(other);
112 }
113
114 explicit operator signed long long() const noexcept
115 {
116 auto e = exponent();
117 auto m = mantissa();
118
119 while (e < 0) {
120 m /= 10;
121 e++;
122 }
123
124 while (e > 0) {
125 m *= 10;
126 e--;
127 if (!is_valid_mantissa(m)) {
129 }
130 }
131 return m;
132 }
133
134 explicit operator signed long() const noexcept
135 {
136 return narrow_cast<signed long>(static_cast<signed long long>(*this));
137 }
138 explicit operator signed int() const noexcept
139 {
140 return narrow_cast<signed int>(static_cast<signed long long>(*this));
141 }
142 explicit operator signed short() const noexcept
143 {
144 return narrow_cast<signed short>(static_cast<signed long long>(*this));
145 }
146 explicit operator signed char() const noexcept
147 {
148 return narrow_cast<signed char>(static_cast<signed long long>(*this));
149 }
150 explicit operator unsigned long long() const noexcept
151 {
152 return narrow_cast<unsigned int>(static_cast<signed long long>(*this));
153 }
154 explicit operator unsigned long() const noexcept
155 {
156 return narrow_cast<unsigned long>(static_cast<signed long long>(*this));
157 }
158 explicit operator unsigned int() const noexcept
159 {
160 return narrow_cast<unsigned int>(static_cast<signed long long>(*this));
161 }
162 explicit operator unsigned short() const noexcept
163 {
164 return narrow_cast<unsigned short>(static_cast<signed long long>(*this));
165 }
166 explicit operator unsigned char() const noexcept
167 {
168 return narrow_cast<unsigned char>(static_cast<signed long long>(*this));
169 }
170 explicit operator long double() const noexcept
171 {
172 return static_cast<long double>(mantissa()) * powl(10.0, exponent());
173 }
174 explicit operator double() const noexcept
175 {
176 return static_cast<double>(static_cast<long double>(*this));
177 }
178 explicit operator float() const noexcept
179 {
180 return static_cast<float>(static_cast<long double>(*this));
181 }
182
183 size_t hash() const noexcept
184 {
185 auto v = this->normalize();
186 return std::hash<uint64_t>{}(v.value);
187 }
188
193 [[nodiscard]] constexpr int exponent() const noexcept
194 {
195 return static_cast<int8_t>(value);
196 }
197
202 [[nodiscard]] constexpr long long mantissa() const noexcept
203 {
204 return static_cast<int64_t>(value) >> 8;
205 }
206
207 [[nodiscard]] constexpr std::pair<int, long long> exponent_mantissa() const noexcept
208 {
209 return {exponent(), mantissa()};
210 }
211
217 [[nodiscard]] constexpr decimal normalize() const noexcept
218 {
219 ttlet[e, m] = exponent_mantissa();
220 ttlet[e_, m_] = decimal::normalize(e, m);
221 return {e_, m_};
222 }
223
224 decimal &operator+=(decimal rhs) noexcept
225 {
226 ttlet[e, lhs_m, rhs_m] = decimal::align(*this, rhs);
227 value = decimal::pack(e, lhs_m + rhs_m);
228 return *this;
229 }
230
231 decimal &operator-=(decimal rhs) noexcept
232 {
233 ttlet[e, lhs_m, rhs_m] = decimal::align(*this, rhs);
234 value = decimal::pack(e, lhs_m - rhs_m);
235 return *this;
236 }
237
238 decimal &operator*=(decimal rhs) noexcept
239 {
240 return *this = *this * rhs;
241 }
242
243 decimal &operator/=(decimal rhs) noexcept
244 {
245 return *this = *this / rhs;
246 }
247
248public:
249 [[nodiscard]] friend bool operator==(decimal lhs, decimal rhs) noexcept
250 {
251 ttlet[e, lhs_m, rhs_m] = decimal::align(lhs, rhs);
252 return lhs_m == rhs_m;
253 }
254
255 [[nodiscard]] friend auto operator<=>(decimal lhs, decimal rhs) noexcept
256 {
257 ttlet[e, lhs_m, rhs_m] = decimal::align(lhs, rhs);
258 return lhs_m <=> rhs_m;
259 }
260
261 [[nodiscard]] friend constexpr decimal operator-(decimal rhs) noexcept
262 {
263 return {rhs.exponent(), -rhs.mantissa()};
264 }
265
266 [[nodiscard]] friend decimal operator+(decimal lhs, decimal rhs) noexcept
267 {
268 ttlet[e, lhs_m, rhs_m] = decimal::align(lhs, rhs);
269 return {e, lhs_m + rhs_m};
270 }
271
272 [[nodiscard]] friend decimal operator-(decimal lhs, decimal rhs) noexcept
273 {
274 ttlet[e, lhs_m, rhs_m] = decimal::align(lhs, rhs);
275 return {e, lhs_m - rhs_m};
276 }
277
278 [[nodiscard]] friend decimal operator*(decimal lhs, decimal rhs) noexcept
279 {
280 auto lhs_e = lhs.exponent();
281 auto lhs_m = lhs.mantissa();
282 auto rhs_e = rhs.exponent();
283 auto rhs_m = rhs.mantissa();
284
285 long long m = 0;
286 if (!mul_overflow(lhs_m, rhs_m, &m)) {
287 [[likely]] return {lhs_e + rhs_e, m};
288 }
289
290 // Try with normalized decimals, this is without loss of precision.
291 std::tie(lhs_e, lhs_m) = decimal::normalize(lhs_e, lhs_m);
292 std::tie(rhs_e, rhs_m) = decimal::normalize(rhs_e, rhs_m);
293 while (mul_overflow(lhs_m, rhs_m, &m)) {
294 // Reduce precision of largest mantissa until multiplication succeeds.
295 if (lhs_m > rhs_m) {
296 lhs_m += 5;
297 lhs_m /= 10;
298 lhs_e++;
299 } else {
300 rhs_m += 5;
301 rhs_m /= 10;
302 rhs_e++;
303 }
304 }
305 return {lhs_e + rhs_e, m};
306 }
307
308 [[nodiscard]] friend decimal operator/(decimal lhs, decimal rhs) noexcept
309 {
310 auto rhs_m = rhs.mantissa();
311 tt_axiom(rhs_m != 0);
312 auto rhs_e = rhs.exponent();
313 auto lhs_m = lhs.mantissa();
314 auto lhs_e = lhs.exponent();
315
316 std::tie(lhs_e, lhs_m) = decimal::denormalize(lhs_e, lhs_m);
317 return {lhs_e - rhs_e, lhs_m / rhs_m};
318 }
319
320 [[nodiscard]] friend decimal operator%(decimal lhs, decimal rhs) noexcept
321 {
322 auto rhs_m = rhs.mantissa();
323 tt_axiom(rhs_m != 0);
324 auto rhs_e = rhs.exponent();
325 auto lhs_m = lhs.mantissa();
326 auto lhs_e = lhs.exponent();
327
328 std::tie(lhs_e, lhs_m) = decimal::denormalize(lhs_e, lhs_m);
329 return {lhs_e - rhs_e, lhs_m % rhs_m};
330 }
331
332 [[nodiscard]] friend std::string to_string(decimal x) noexcept
333 {
334 auto [e, m] = x.exponent_mantissa();
335 auto s = std::to_string(std::abs(m));
336
337 auto decimal_position = -e;
338 auto leading_zeros = (decimal_position - std::ssize(s)) + 1;
339 if (leading_zeros > 0) {
340 s.insert(0, leading_zeros, '0');
341 }
342
343 auto trailing_zeros = e;
344 if (trailing_zeros > 0) {
345 s.append(trailing_zeros, '0');
346 }
347
348 if (decimal_position > 0) {
349 s.insert(s.size() - decimal_position, 1, '.');
350 }
351
352 if (m < 0) {
353 s.insert(0, 1, '-');
354 }
355
356 return s;
357 }
358
359 friend std::ostream &operator<<(std::ostream &lhs, decimal rhs)
360 {
361 return lhs << to_string(rhs);
362 }
363
364private:
367 [[nodiscard]] constexpr static std::pair<int, long long> normalize(int e, long long m) noexcept
368 {
369 if (m != 0) {
370 while (m % 10 == 0) {
371 m /= 10;
372 e++;
373 }
374 }
375 return {e, m};
376 }
377
380 [[nodiscard]] constexpr static std::pair<int, long long> denormalize(int e, long long m) noexcept
381 {
382 if (m != 0) {
383 // The mantissa is allowed to go slightly over the maximum; since it
384 // is used mostly for the rhs of a division, which means the result will,
385 // in all probability, make the mantissa smaller than the maximum.
386 while (is_valid_mantissa(m)) {
387 m *= 10;
388 e--;
389 }
390 }
391 return {e, m};
392 }
393
397 [[nodiscard]] constexpr static bool is_valid_mantissa(long long m) noexcept
398 {
399 m >>= (mantissa_bits - 1);
400 return m == 0 || m == -1;
401 }
402
406 [[nodiscard]] constexpr static bool is_valid_exponent(int e) noexcept
407 {
408 e >>= (exponent_bits - 1);
409 return e == 0 || e == -1;
410 }
411
412 [[nodiscard]] static std::tuple<int, long long, long long> align(decimal lhs, decimal rhs) noexcept
413 {
414 auto lhs_e = lhs.exponent();
415 auto lhs_m = lhs.mantissa();
416 auto rhs_e = rhs.exponent();
417 auto rhs_m = rhs.mantissa();
418
419 if (lhs_e == rhs_e) {
420 // No alignment needed.
421 } else if (lhs_e > rhs_e) {
422 do {
423 lhs_m *= 10;
424 lhs_e--;
425 if (!is_valid_mantissa(lhs_m)) {
426 while (lhs_e > rhs_e) {
427 rhs_m /= 10;
428 rhs_e++;
429 }
430 break;
431 }
432 } while (lhs_e > rhs_e);
433 } else {
434 do {
435 rhs_m *= 10;
436 rhs_e--;
437 if (!is_valid_mantissa(lhs_m)) {
438 while (lhs_e < rhs_e) {
439 lhs_m /= 10;
440 lhs_e++;
441 }
442 break;
443 }
444 } while (lhs_e < rhs_e);
445 }
446 return {lhs_e, lhs_m, rhs_m};
447 }
448
451 [[nodiscard]] constexpr static uint64_t pack(int e, long long m) noexcept
452 {
453 // Adjust an mantissa that is too large. Precision may be lost.
454 while (!is_valid_mantissa(m)) {
455 [[unlikely]] m /= 10;
456 e++;
457 tt_assert(e <= exponent_max);
458 }
459
460 while (e > exponent_max) {
461 [[unlikely]] if ((m *= 10) == 0)
462 {
463 e = exponent_max;
464 break;
465 }
466 e--;
467
468 // abort on overflow. This decimal does not support infinite.
469 tt_assert(is_valid_mantissa(m));
470 }
471
472 while (e < exponent_min) {
473 [[unlikely]] if ((m /= 10) == 0)
474 {
475 e = exponent_min;
476 break;
477 }
478 e++;
479 }
480
481 return static_cast<uint64_t>(m) << exponent_bits | static_cast<uint8_t>(e);
482 }
483
484 [[nodiscard]] static std::pair<int, long long> to_exponent_mantissa(double x) noexcept
485 {
486 uint64_t x_;
487 std::memcpy(&x_, &x, sizeof(x_));
488
489 auto e2 = static_cast<int>((x_ << 1) >> 53) - 1023 - 52;
490 auto m = static_cast<long long>((x_ << 12) >> 12);
491 if (e2 > (-1023 - 52)) {
492 // Add leading '1'.
493 m |= (1LL << 52);
494 }
495
496 if (static_cast<int64_t>(x_) < 0) {
497 m = -m;
498 }
499
500 if (m == 0) {
501 return {0, 0};
502 }
503
504 int e10 = 0;
505 while (e2 < 0) {
506 while (is_valid_mantissa(m)) {
507 m *= 10;
508 e10--;
509 }
510 m /= 2;
511 e2++;
512 }
513
514 while (e2 > 0) {
515 while (!is_valid_mantissa(m)) {
516 m /= 10;
517 e10++;
518 }
519 m *= 2;
520 e2--;
521 }
522
523 return {e10, m};
524 }
525
526 [[nodiscard]] std::pair<int, long long> to_exponent_mantissa(std::string_view str)
527 {
528 std::string mantissa_str;
529
530 int nr_digits = 0;
531 int nr_digits_in_front_of_point = -1;
532 for (ttlet c : str) {
533 if (c >= '0' && c <= '9') {
534 mantissa_str += c;
535 nr_digits++;
536 } else if (c == '.') {
537 nr_digits_in_front_of_point = nr_digits;
538 } else if (c == '\'' || c == ',') {
539 // Ignore thousand separators.
540 } else if (c == '-') {
541 mantissa_str += c;
542 } else {
543 throw parse_error("Unexpected character in decimal number '{}'", str);
544 }
545 }
546
547 int exponent = (nr_digits_in_front_of_point >= 0) ? (nr_digits_in_front_of_point - nr_digits) : 0;
548
549 auto first = mantissa_str.data();
550 auto last = first + mantissa_str.size();
551 long long mantissa;
552 auto result = std::from_chars(first, last, mantissa, 10);
553 if (result.ptr == first) {
554 throw parse_error("Could not parse mantissa '{}'", mantissa_str);
555 } else if (result.ec == std::errc::result_out_of_range) {
556 throw parse_error("Mantissa '{}' out of range ", mantissa_str);
557 } else {
558 return {exponent, mantissa};
559 }
560 }
561};
562
563} // namespace tt
564
565namespace std {
566
567template<>
568struct hash<tt::decimal> {
569 inline size_t operator()(tt::decimal const &value) const
570 {
571 return value.hash();
572 }
573};
574
575template<typename CharT>
576struct std::formatter<tt::decimal, CharT> : std::formatter<double, CharT> {
577 auto format(tt::decimal const &t, auto &fc)
578 {
579 return std::formatter<double, CharT>::format(static_cast<double>(t), fc);
580 }
581};
582
583} // namespace std
STL namespace.
Definition decimal.hpp:18
constexpr decimal normalize() const noexcept
Return a normalized decimal.
Definition decimal.hpp:217
constexpr long long mantissa() const noexcept
Extract mantissa from value.
Definition decimal.hpp:202
constexpr int exponent() const noexcept
Extract exponent from value.
Definition decimal.hpp:193
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)