HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
decimal.hpp
1// Copyright Take Vos 2019-2022.
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 "utility/module.hpp"
8#include "int_overflow.hpp"
9#include <limits>
10#include <string_view>
11#include <string>
12#include <charconv>
13#include <ostream>
14#include <bit>
15
16namespace hi::inline v1 {
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 = narrow_cast<signed long long>(other);
80 }
81 constexpr decimal& operator=(signed int other) noexcept
82 {
83 return *this = narrow_cast<signed long long>(other);
84 }
85 constexpr decimal& operator=(signed short other) noexcept
86 {
87 return *this = narrow_cast<signed long long>(other);
88 }
89 constexpr decimal& operator=(signed char other) noexcept
90 {
91 return *this = narrow_cast<signed long long>(other);
92 }
93 constexpr decimal& operator=(unsigned long long other) noexcept
94 {
95 return *this = narrow_cast<signed long long>(other);
96 }
97 constexpr decimal& operator=(unsigned long other) noexcept
98 {
99 return *this = narrow_cast<signed long long>(other);
100 }
101 constexpr decimal& operator=(unsigned int other) noexcept
102 {
103 return *this = narrow_cast<signed long long>(other);
104 }
105 constexpr decimal& operator=(unsigned short other) noexcept
106 {
107 return *this = narrow_cast<signed long long>(other);
108 }
109 constexpr decimal& operator=(unsigned char other) noexcept
110 {
111 return *this = narrow_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 explicit operator bool() const noexcept
184 {
185 return mantissa() != 0;
186 }
187
188 std::size_t hash() const noexcept
189 {
190 hilet v = this->normalize();
191 return std::hash<uint64_t>{}(v.value);
192 }
193
198 [[nodiscard]] constexpr int exponent() const noexcept
199 {
200 return truncate<int8_t>(value);
201 }
202
207 [[nodiscard]] constexpr long long mantissa() const noexcept
208 {
209 return truncate<int64_t>(value) >> exponent_bits;
210 }
211
212 [[nodiscard]] constexpr std::pair<int, long long> exponent_mantissa() const noexcept
213 {
214 return {exponent(), mantissa()};
215 }
216
222 [[nodiscard]] constexpr decimal normalize() const noexcept
223 {
224 hilet[e, m] = exponent_mantissa();
225 hilet[e_, m_] = decimal::normalize(e, m);
226 return {e_, m_};
227 }
228
229 decimal& operator+=(decimal rhs) noexcept
230 {
231 hilet[e, lhs_m, rhs_m] = decimal::align(*this, rhs);
232 value = decimal::pack(e, lhs_m + rhs_m);
233 return *this;
234 }
235
236 decimal& operator-=(decimal rhs) noexcept
237 {
238 hilet[e, lhs_m, rhs_m] = decimal::align(*this, rhs);
239 value = decimal::pack(e, lhs_m - rhs_m);
240 return *this;
241 }
242
243 decimal& operator*=(decimal rhs) noexcept
244 {
245 return *this = *this * rhs;
246 }
247
248 decimal& operator/=(decimal rhs) noexcept
249 {
250 return *this = *this / rhs;
251 }
252
253public:
254 [[nodiscard]] friend bool operator==(decimal lhs, decimal rhs) noexcept
255 {
256 hilet[e, lhs_m, rhs_m] = decimal::align(lhs, rhs);
257 return lhs_m == rhs_m;
258 }
259
260 [[nodiscard]] friend auto operator<=>(decimal lhs, decimal rhs) noexcept
261 {
262 hilet[e, lhs_m, rhs_m] = decimal::align(lhs, rhs);
263 return lhs_m <=> rhs_m;
264 }
265
266 [[nodiscard]] friend constexpr decimal operator-(decimal rhs) noexcept
267 {
268 return {rhs.exponent(), -rhs.mantissa()};
269 }
270
271 [[nodiscard]] friend decimal operator+(decimal lhs, decimal rhs) noexcept
272 {
273 hilet[e, lhs_m, rhs_m] = decimal::align(lhs, rhs);
274 return {e, lhs_m + rhs_m};
275 }
276
277 [[nodiscard]] friend decimal operator-(decimal lhs, decimal rhs) noexcept
278 {
279 hilet[e, lhs_m, rhs_m] = decimal::align(lhs, rhs);
280 return {e, lhs_m - rhs_m};
281 }
282
283 [[nodiscard]] friend decimal operator*(decimal lhs, decimal rhs) noexcept
284 {
285 auto lhs_e = lhs.exponent();
286 auto lhs_m = lhs.mantissa();
287 auto rhs_e = rhs.exponent();
288 auto rhs_m = rhs.mantissa();
289
290 long long m = 0;
291 if (!mul_overflow(lhs_m, rhs_m, &m)) {
292 [[likely]] return {lhs_e + rhs_e, m};
293 }
294
295 // Try with normalized decimals, this is without loss of precision.
296 std::tie(lhs_e, lhs_m) = decimal::normalize(lhs_e, lhs_m);
297 std::tie(rhs_e, rhs_m) = decimal::normalize(rhs_e, rhs_m);
298 while (mul_overflow(lhs_m, rhs_m, &m)) {
299 // Reduce precision of largest mantissa until multiplication succeeds.
300 if (lhs_m > rhs_m) {
301 lhs_m += 5;
302 lhs_m /= 10;
303 lhs_e++;
304 } else {
305 rhs_m += 5;
306 rhs_m /= 10;
307 rhs_e++;
308 }
309 }
310 return {lhs_e + rhs_e, m};
311 }
312
313 [[nodiscard]] friend decimal operator/(decimal lhs, decimal rhs) noexcept
314 {
315 hilet rhs_m = rhs.mantissa();
316 hi_axiom(rhs_m != 0);
317 hilet rhs_e = rhs.exponent();
318 auto lhs_m = lhs.mantissa();
319 auto lhs_e = lhs.exponent();
320
321 std::tie(lhs_e, lhs_m) = decimal::denormalize(lhs_e, lhs_m);
322 return {lhs_e - rhs_e, lhs_m / rhs_m};
323 }
324
325 [[nodiscard]] friend decimal operator%(decimal lhs, decimal rhs) noexcept
326 {
327 hilet rhs_m = rhs.mantissa();
328 hi_axiom(rhs_m != 0);
329 hilet rhs_e = rhs.exponent();
330 auto lhs_m = lhs.mantissa();
331 auto lhs_e = lhs.exponent();
332
333 std::tie(lhs_e, lhs_m) = decimal::denormalize(lhs_e, lhs_m);
334 return {lhs_e - rhs_e, lhs_m % rhs_m};
335 }
336
337 [[nodiscard]] friend std::string to_string(decimal x) noexcept
338 {
339 hilet[e, m] = x.exponent_mantissa();
340 auto s = std::to_string(std::abs(m));
341
342 hilet decimal_position = -e;
343 hilet leading_zeros = (decimal_position - ssize(s)) + 1;
344 if (leading_zeros > 0) {
345 s.insert(0, leading_zeros, '0');
346 }
347
348 hilet trailing_zeros = e;
349 if (trailing_zeros > 0) {
350 s.append(trailing_zeros, '0');
351 }
352
353 if (decimal_position > 0) {
354 s.insert(s.size() - decimal_position, 1, '.');
355 }
356
357 if (m < 0) {
358 s.insert(0, 1, '-');
359 }
360
361 return s;
362 }
363
364 friend std::ostream& operator<<(std::ostream& lhs, decimal rhs)
365 {
366 return lhs << to_string(rhs);
367 }
368
369private:
372 [[nodiscard]] constexpr static std::pair<int, long long> normalize(int e, long long m) noexcept
373 {
374 if (m != 0) {
375 while (m % 10 == 0) {
376 m /= 10;
377 e++;
378 }
379 }
380 return {e, m};
381 }
382
385 [[nodiscard]] constexpr static std::pair<int, long long> denormalize(int e, long long m) noexcept
386 {
387 if (m != 0) {
388 // The mantissa is allowed to go slightly over the maximum; since it
389 // is used mostly for the rhs of a division, which means the result will,
390 // in all probability, make the mantissa smaller than the maximum.
391 while (is_valid_mantissa(m)) {
392 m *= 10;
393 e--;
394 }
395 }
396 return {e, m};
397 }
398
402 [[nodiscard]] constexpr static bool is_valid_mantissa(long long m) noexcept
403 {
404 m >>= (mantissa_bits - 1);
405 return m == 0 || m == -1;
406 }
407
411 [[nodiscard]] constexpr static bool is_valid_exponent(int e) noexcept
412 {
413 e >>= (exponent_bits - 1);
414 return e == 0 || e == -1;
415 }
416
417 [[nodiscard]] static std::tuple<int, long long, long long> align(decimal lhs, decimal rhs) noexcept
418 {
419 auto lhs_e = lhs.exponent();
420 auto lhs_m = lhs.mantissa();
421 auto rhs_e = rhs.exponent();
422 auto rhs_m = rhs.mantissa();
423
424 if (lhs_e == rhs_e) {
425 // No alignment needed.
426 } else if (lhs_e > rhs_e) {
427 do {
428 lhs_m *= 10;
429 lhs_e--;
430 if (!is_valid_mantissa(lhs_m)) {
431 while (lhs_e > rhs_e) {
432 rhs_m /= 10;
433 rhs_e++;
434 }
435 break;
436 }
437 } while (lhs_e > rhs_e);
438 } else {
439 do {
440 rhs_m *= 10;
441 rhs_e--;
442 if (!is_valid_mantissa(lhs_m)) {
443 while (lhs_e < rhs_e) {
444 lhs_m /= 10;
445 lhs_e++;
446 }
447 break;
448 }
449 } while (lhs_e < rhs_e);
450 }
451 return {lhs_e, lhs_m, rhs_m};
452 }
453
456 [[nodiscard]] constexpr static uint64_t pack(int e, long long m) noexcept
457 {
458 // Adjust an mantissa that is too large. Precision may be lost.
459 while (!is_valid_mantissa(m)) {
460 [[unlikely]] m /= 10;
461 e++;
462 hi_assert(e <= exponent_max);
463 }
464
465 while (e > exponent_max) {
466 [[unlikely]] if ((m *= 10) == 0)
467 {
468 e = exponent_max;
469 break;
470 }
471 e--;
472
473 // abort on overflow. This decimal does not support infinite.
474 hi_assert(is_valid_mantissa(m));
475 }
476
477 while (e < exponent_min) {
478 [[unlikely]] if ((m /= 10) == 0)
479 {
480 e = exponent_min;
481 break;
482 }
483 e++;
484 }
485
486 return std::bit_cast<uint64_t>(narrow_cast<int64_t>(m) << exponent_bits) | std::bit_cast<uint8_t>(narrow_cast<int8_t>(e));
487 }
488
489 [[nodiscard]] static std::pair<int, long long> to_exponent_mantissa(double x) noexcept
490 {
491 uint64_t x_;
492 std::memcpy(&x_, &x, sizeof(x_));
493
494 auto e2 = narrow_cast<int>((x_ << 1) >> 53) - 1023 - 52;
495 auto m = narrow_cast<long long>((x_ << 12) >> 12);
496 if (e2 > (-1023 - 52)) {
497 // Add leading '1'.
498 m |= (1LL << 52);
499 }
500
501 if (narrow_cast<int64_t>(x_) < 0) {
502 m = -m;
503 }
504
505 if (m == 0) {
506 return {0, 0};
507 }
508
509 int e10 = 0;
510 while (e2 < 0) {
511 while (is_valid_mantissa(m)) {
512 m *= 10;
513 e10--;
514 }
515 m /= 2;
516 e2++;
517 }
518
519 while (e2 > 0) {
520 while (!is_valid_mantissa(m)) {
521 m /= 10;
522 e10++;
523 }
524 m *= 2;
525 e2--;
526 }
527
528 return {e10, m};
529 }
530
531 [[nodiscard]] std::pair<int, long long> to_exponent_mantissa(std::string_view str)
532 {
533 std::string mantissa_str;
534
535 int nr_digits = 0;
536 int nr_digits_in_front_of_point = -1;
537 for (hilet c : str) {
538 if (c >= '0' && c <= '9') {
539 mantissa_str += c;
540 nr_digits++;
541 } else if (c == '.') {
542 nr_digits_in_front_of_point = nr_digits;
543 } else if (c == '\'' || c == ',') {
544 // Ignore thousand separators.
545 } else if (c == '-') {
546 mantissa_str += c;
547 } else {
548 throw parse_error(std::format("Unexpected character in decimal number '{}'", str));
549 }
550 }
551
552 int exponent = (nr_digits_in_front_of_point >= 0) ? (nr_digits_in_front_of_point - nr_digits) : 0;
553
554 auto first = mantissa_str.data();
555 auto last = first + mantissa_str.size();
556 long long mantissa;
557 hilet result = std::from_chars(first, last, mantissa, 10);
558 if (result.ptr == first) {
559 throw parse_error(std::format("Could not parse mantissa '{}'", mantissa_str));
560 } else if (result.ec == std::errc::result_out_of_range) {
561 throw parse_error(std::format("Mantissa '{}' out of range ", mantissa_str));
562 } else {
563 return {exponent, mantissa};
564 }
565 }
566};
567
568} // namespace hi::inline v1
569
570template<>
571struct std::hash<hi::decimal> {
572 inline std::size_t operator()(hi::decimal const& value) const noexcept
573 {
574 return value.hash();
575 }
576};
577
578template<typename CharT>
579struct std::formatter<hi::decimal, CharT> : std::formatter<double, CharT> {
580 auto format(hi::decimal const& t, auto& fc)
581 {
582 return std::formatter<double, CharT>::format(static_cast<double>(t), fc);
583 }
584};
#define hi_assert(expression,...)
Assert if expression is true.
Definition assert.hpp:199
#define hi_axiom(expression,...)
Specify an axiom; an expression that is true.
Definition assert.hpp:253
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
DOXYGEN BUG.
Definition algorithm.hpp:13
bool mul_overflow(T lhs, T rhs, T *r) noexcept
Multiply with overflow detection.
Definition int_overflow.hpp:92
geometry/margins.hpp
Definition cache.hpp:11
Definition decimal.hpp:18
constexpr int exponent() const noexcept
Extract exponent from value.
Definition decimal.hpp:198
constexpr long long mantissa() const noexcept
Extract mantissa from value.
Definition decimal.hpp:207
constexpr decimal normalize() const noexcept
Return a normalized decimal.
Definition decimal.hpp:222
T align(T... args)
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)