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/utility.hpp"
8#include "../macros.hpp"
9#include <limits>
10#include <string_view>
11#include <string>
12#include <charconv>
13#include <ostream>
14#include <bit>
15
16hi_export_module(hikogui.numeric.decimal);
17
18hi_export namespace hi::inline v1 {
19
20class decimal {
21private:
22 /* Value contains an 8 bit signed exponent in the most-significant bits
23 * and a 56 bit signed mantissa in the least-significant bits.
24 */
25 uint64_t value;
26
27public:
28 constexpr static int mantissa_bits = 56;
29 constexpr static int exponent_bits = 8;
30 constexpr static int exponent_max = 127;
31 constexpr static int exponent_min = -128;
32
33 constexpr decimal() noexcept : value(0) {}
34 constexpr decimal(decimal const& other) noexcept = default;
35 constexpr decimal(decimal&& other) noexcept = default;
36 constexpr decimal& operator=(decimal const& other) noexcept = default;
37 constexpr decimal& operator=(decimal&& other) noexcept = default;
38
39 constexpr decimal(int exponent, long long mantissa) noexcept : value(decimal::pack(exponent, mantissa)) {}
40
41 constexpr decimal(std::pair<int, long long> exponent_mantissa) : 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
58 {
59 value = decimal::pack(other.first, other.second);
60 return *this;
61 }
62 decimal& operator=(std::string_view str) noexcept
63 {
64 return *this = to_exponent_mantissa(str);
65 }
66 constexpr decimal& operator=(double other) noexcept
67 {
68 return *this = to_exponent_mantissa(other);
69 }
70 constexpr decimal& operator=(float other) noexcept
71 {
72 return *this = to_exponent_mantissa(other);
73 }
74 constexpr decimal& operator=(signed long long other) noexcept
75 {
76 value = decimal::pack(0, other);
77 return *this;
78 }
79 constexpr decimal& operator=(signed long other) noexcept
80 {
81 return *this = narrow_cast<signed long long>(other);
82 }
83 constexpr decimal& operator=(signed int other) noexcept
84 {
85 return *this = narrow_cast<signed long long>(other);
86 }
87 constexpr decimal& operator=(signed short other) noexcept
88 {
89 return *this = narrow_cast<signed long long>(other);
90 }
91 constexpr decimal& operator=(signed char other) noexcept
92 {
93 return *this = narrow_cast<signed long long>(other);
94 }
95 constexpr decimal& operator=(unsigned long long other) noexcept
96 {
97 return *this = narrow_cast<signed long long>(other);
98 }
99 constexpr decimal& operator=(unsigned long other) noexcept
100 {
101 return *this = narrow_cast<signed long long>(other);
102 }
103 constexpr decimal& operator=(unsigned int other) noexcept
104 {
105 return *this = narrow_cast<signed long long>(other);
106 }
107 constexpr decimal& operator=(unsigned short other) noexcept
108 {
109 return *this = narrow_cast<signed long long>(other);
110 }
111 constexpr decimal& operator=(unsigned char other) noexcept
112 {
113 return *this = narrow_cast<signed long long>(other);
114 }
115
116 explicit operator signed long long() const noexcept
117 {
118 auto e = exponent();
119 auto m = mantissa();
120
121 while (e < 0) {
122 m /= 10;
123 e++;
124 }
125
126 while (e > 0) {
127 m *= 10;
128 e--;
129 if (!is_valid_mantissa(m)) {
131 }
132 }
133 return m;
134 }
135
136 explicit operator signed long() const noexcept
137 {
138 return narrow_cast<signed long>(static_cast<signed long long>(*this));
139 }
140 explicit operator signed int() const noexcept
141 {
142 return narrow_cast<signed int>(static_cast<signed long long>(*this));
143 }
144 explicit operator signed short() const noexcept
145 {
146 return narrow_cast<signed short>(static_cast<signed long long>(*this));
147 }
148 explicit operator signed char() const noexcept
149 {
150 return narrow_cast<signed char>(static_cast<signed long long>(*this));
151 }
152 explicit operator unsigned long long() const noexcept
153 {
154 return narrow_cast<unsigned int>(static_cast<signed long long>(*this));
155 }
156 explicit operator unsigned long() const noexcept
157 {
158 return narrow_cast<unsigned long>(static_cast<signed long long>(*this));
159 }
160 explicit operator unsigned int() const noexcept
161 {
162 return narrow_cast<unsigned int>(static_cast<signed long long>(*this));
163 }
164 explicit operator unsigned short() const noexcept
165 {
166 return narrow_cast<unsigned short>(static_cast<signed long long>(*this));
167 }
168 explicit operator unsigned char() const noexcept
169 {
170 return narrow_cast<unsigned char>(static_cast<signed long long>(*this));
171 }
172 explicit operator long double() const noexcept
173 {
174 return static_cast<long double>(mantissa()) * powl(10.0, exponent());
175 }
176 explicit operator double() const noexcept
177 {
178 return static_cast<double>(static_cast<long double>(*this));
179 }
180 explicit operator float() const noexcept
181 {
182 return static_cast<float>(static_cast<long double>(*this));
183 }
184
185 explicit operator bool() const noexcept
186 {
187 return mantissa() != 0;
188 }
189
190 std::size_t hash() const noexcept
191 {
192 auto const v = this->normalize();
193 return std::hash<uint64_t>{}(v.value);
194 }
195
200 [[nodiscard]] constexpr int exponent() const noexcept
201 {
202 return truncate<int8_t>(value);
203 }
204
209 [[nodiscard]] constexpr long long mantissa() const noexcept
210 {
211 return truncate<int64_t>(value) >> exponent_bits;
212 }
213
214 [[nodiscard]] constexpr std::pair<int, long long> exponent_mantissa() const noexcept
215 {
216 return {exponent(), mantissa()};
217 }
218
224 [[nodiscard]] constexpr decimal normalize() const noexcept
225 {
226 auto const[e, m] = exponent_mantissa();
227 auto const[e_, m_] = decimal::normalize(e, m);
228 return {e_, m_};
229 }
230
231 decimal& operator+=(decimal rhs) noexcept
232 {
233 auto const[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 auto const[e, lhs_m, rhs_m] = decimal::align(*this, rhs);
241 value = decimal::pack(e, lhs_m - rhs_m);
242 return *this;
243 }
244
245 decimal& operator*=(decimal rhs) noexcept
246 {
247 return *this = *this * rhs;
248 }
249
250 decimal& operator/=(decimal rhs) noexcept
251 {
252 return *this = *this / rhs;
253 }
254
255public:
256 [[nodiscard]] friend bool operator==(decimal lhs, decimal rhs) noexcept
257 {
258 auto const[e, lhs_m, rhs_m] = decimal::align(lhs, rhs);
259 return lhs_m == rhs_m;
260 }
261
262 [[nodiscard]] friend auto operator<=>(decimal lhs, decimal rhs) noexcept
263 {
264 auto const[e, lhs_m, rhs_m] = decimal::align(lhs, rhs);
265 return lhs_m <=> rhs_m;
266 }
267
268 [[nodiscard]] friend constexpr decimal operator-(decimal rhs) noexcept
269 {
270 return {rhs.exponent(), -rhs.mantissa()};
271 }
272
273 [[nodiscard]] friend decimal operator+(decimal lhs, decimal rhs) noexcept
274 {
275 auto const[e, lhs_m, rhs_m] = decimal::align(lhs, rhs);
276 return {e, lhs_m + rhs_m};
277 }
278
279 [[nodiscard]] friend decimal operator-(decimal lhs, decimal rhs) noexcept
280 {
281 auto const[e, lhs_m, rhs_m] = decimal::align(lhs, rhs);
282 return {e, lhs_m - rhs_m};
283 }
284
285 [[nodiscard]] friend decimal operator*(decimal lhs, decimal rhs) noexcept
286 {
287 auto lhs_e = lhs.exponent();
288 auto lhs_m = lhs.mantissa();
289 auto rhs_e = rhs.exponent();
290 auto rhs_m = rhs.mantissa();
291
292 long long m = 0;
293 if (not mul_overflow(lhs_m, rhs_m, &m)) {
294 [[likely]] return {lhs_e + rhs_e, m};
295 }
296
297 // Try with normalized decimals, this is without loss of precision.
298 std::tie(lhs_e, lhs_m) = decimal::normalize(lhs_e, lhs_m);
299 std::tie(rhs_e, rhs_m) = decimal::normalize(rhs_e, rhs_m);
300 while (mul_overflow(lhs_m, rhs_m, &m)) {
301 // Reduce precision of largest mantissa until multiplication succeeds.
302 if (lhs_m > rhs_m) {
303 lhs_m += 5;
304 lhs_m /= 10;
305 lhs_e++;
306 } else {
307 rhs_m += 5;
308 rhs_m /= 10;
309 rhs_e++;
310 }
311 }
312 return {lhs_e + rhs_e, m};
313 }
314
315 [[nodiscard]] friend decimal operator/(decimal lhs, decimal rhs) noexcept
316 {
317 auto const rhs_m = rhs.mantissa();
318 hi_axiom(rhs_m != 0);
319 auto const rhs_e = rhs.exponent();
320 auto lhs_m = lhs.mantissa();
321 auto lhs_e = lhs.exponent();
322
323 std::tie(lhs_e, lhs_m) = decimal::denormalize(lhs_e, lhs_m);
324 return {lhs_e - rhs_e, lhs_m / rhs_m};
325 }
326
327 [[nodiscard]] friend decimal operator%(decimal lhs, decimal rhs) noexcept
328 {
329 auto const rhs_m = rhs.mantissa();
330 hi_axiom(rhs_m != 0);
331 auto const rhs_e = rhs.exponent();
332 auto lhs_m = lhs.mantissa();
333 auto lhs_e = lhs.exponent();
334
335 std::tie(lhs_e, lhs_m) = decimal::denormalize(lhs_e, lhs_m);
336 return {lhs_e - rhs_e, lhs_m % rhs_m};
337 }
338
339 [[nodiscard]] friend std::string to_string(decimal x) noexcept
340 {
341 auto const[e, m] = x.exponent_mantissa();
342 auto s = std::to_string(std::abs(m));
343
344 auto const decimal_position = -e;
345 auto const leading_zeros = (decimal_position - ssize(s)) + 1;
346 if (leading_zeros > 0) {
347 s.insert(0, leading_zeros, '0');
348 }
349
350 auto const trailing_zeros = e;
351 if (trailing_zeros > 0) {
352 s.append(trailing_zeros, '0');
353 }
354
355 if (decimal_position > 0) {
356 s.insert(s.size() - decimal_position, 1, '.');
357 }
358
359 if (m < 0) {
360 s.insert(0, 1, '-');
361 }
362
363 return s;
364 }
365
366 friend std::ostream& operator<<(std::ostream& lhs, decimal rhs)
367 {
368 return lhs << to_string(rhs);
369 }
370
371private:
374 [[nodiscard]] constexpr static std::pair<int, long long> normalize(int e, long long m) noexcept
375 {
376 if (m != 0) {
377 while (m % 10 == 0) {
378 m /= 10;
379 e++;
380 }
381 }
382 return {e, m};
383 }
384
387 [[nodiscard]] constexpr static std::pair<int, long long> denormalize(int e, long long m) noexcept
388 {
389 if (m != 0) {
390 // The mantissa is allowed to go slightly over the maximum; since it
391 // is used mostly for the rhs of a division, which means the result will,
392 // in all probability, make the mantissa smaller than the maximum.
393 while (is_valid_mantissa(m)) {
394 m *= 10;
395 e--;
396 }
397 }
398 return {e, m};
399 }
400
404 [[nodiscard]] constexpr static bool is_valid_mantissa(long long m) noexcept
405 {
406 m >>= (mantissa_bits - 1);
407 return m == 0 || m == -1;
408 }
409
413 [[nodiscard]] constexpr static bool is_valid_exponent(int e) noexcept
414 {
415 e >>= (exponent_bits - 1);
416 return e == 0 || e == -1;
417 }
418
419 [[nodiscard]] static std::tuple<int, long long, long long> align(decimal lhs, decimal rhs) noexcept
420 {
421 auto lhs_e = lhs.exponent();
422 auto lhs_m = lhs.mantissa();
423 auto rhs_e = rhs.exponent();
424 auto rhs_m = rhs.mantissa();
425
426 if (lhs_e == rhs_e) {
427 // No alignment needed.
428 } else if (lhs_e > rhs_e) {
429 do {
430 lhs_m *= 10;
431 lhs_e--;
432 if (!is_valid_mantissa(lhs_m)) {
433 while (lhs_e > rhs_e) {
434 rhs_m /= 10;
435 rhs_e++;
436 }
437 break;
438 }
439 } while (lhs_e > rhs_e);
440 } else {
441 do {
442 rhs_m *= 10;
443 rhs_e--;
444 if (!is_valid_mantissa(lhs_m)) {
445 while (lhs_e < rhs_e) {
446 lhs_m /= 10;
447 lhs_e++;
448 }
449 break;
450 }
451 } while (lhs_e < rhs_e);
452 }
453 return {lhs_e, lhs_m, rhs_m};
454 }
455
458 [[nodiscard]] constexpr static uint64_t pack(int e, long long m) noexcept
459 {
460 // Adjust an mantissa that is too large. Precision may be lost.
461 while (!is_valid_mantissa(m)) {
462 [[unlikely]] m /= 10;
463 e++;
464 hi_assert(e <= exponent_max);
465 }
466
467 while (e > exponent_max) {
468 [[unlikely]] if ((m *= 10) == 0)
469 {
470 e = exponent_max;
471 break;
472 }
473 e--;
474
475 // abort on overflow. This decimal does not support infinite.
476 hi_assert(is_valid_mantissa(m));
477 }
478
479 while (e < exponent_min) {
480 [[unlikely]] if ((m /= 10) == 0)
481 {
482 e = exponent_min;
483 break;
484 }
485 e++;
486 }
487
488 return std::bit_cast<uint64_t>(narrow_cast<int64_t>(m) << exponent_bits) | std::bit_cast<uint8_t>(narrow_cast<int8_t>(e));
489 }
490
491 [[nodiscard]] static std::pair<int, long long> to_exponent_mantissa(double x) noexcept
492 {
493 uint64_t x_;
494 std::memcpy(&x_, &x, sizeof(x_));
495
496 auto e2 = narrow_cast<int>((x_ << 1) >> 53) - 1023 - 52;
497 auto m = narrow_cast<long long>((x_ << 12) >> 12);
498 if (e2 > (-1023 - 52)) {
499 // Add leading '1'.
500 m |= (1LL << 52);
501 }
502
503 if (narrow_cast<int64_t>(x_) < 0) {
504 m = -m;
505 }
506
507 if (m == 0) {
508 return {0, 0};
509 }
510
511 int e10 = 0;
512 while (e2 < 0) {
513 while (is_valid_mantissa(m)) {
514 m *= 10;
515 e10--;
516 }
517 m /= 2;
518 e2++;
519 }
520
521 while (e2 > 0) {
522 while (!is_valid_mantissa(m)) {
523 m /= 10;
524 e10++;
525 }
526 m *= 2;
527 e2--;
528 }
529
530 return {e10, m};
531 }
532
533 [[nodiscard]] std::pair<int, long long> to_exponent_mantissa(std::string_view str)
534 {
535 std::string mantissa_str;
536
537 int nr_digits = 0;
538 int nr_digits_in_front_of_point = -1;
539 for (auto const c : str) {
540 if (c >= '0' && c <= '9') {
541 mantissa_str += c;
542 nr_digits++;
543 } else if (c == '.') {
544 nr_digits_in_front_of_point = nr_digits;
545 } else if (c == '\'' || c == ',') {
546 // Ignore thousand separators.
547 } else if (c == '-') {
548 mantissa_str += c;
549 } else {
550 throw parse_error(std::format("Unexpected character in decimal number '{}'", str));
551 }
552 }
553
554 int exponent = (nr_digits_in_front_of_point >= 0) ? (nr_digits_in_front_of_point - nr_digits) : 0;
555
556 auto first = mantissa_str.data();
557 auto last = first + mantissa_str.size();
558 long long mantissa;
559 auto const result = std::from_chars(first, last, mantissa, 10);
560 if (result.ptr == first) {
561 throw parse_error(std::format("Could not parse mantissa '{}'", mantissa_str));
562 } else if (result.ec == std::errc::result_out_of_range) {
563 throw parse_error(std::format("Mantissa '{}' out of range ", mantissa_str));
564 } else {
565 return {exponent, mantissa};
566 }
567 }
568};
569
570} // namespace hi::inline v1
571
572template<>
573struct std::hash<hi::decimal> {
574 inline std::size_t operator()(hi::decimal const& value) const noexcept
575 {
576 return value.hash();
577 }
578};
579
580// XXX #617 MSVC bug does not handle partial specialization in modules.
581hi_export template<>
582struct std::formatter<hi::decimal, char> : std::formatter<double, char> {
583 auto format(hi::decimal const& t, auto& fc) const
584 {
585 return std::formatter<double, char>::format(static_cast<double>(t), fc);
586 }
587};
The HikoGUI namespace.
Definition array_generic.hpp:20
constexpr Lhs & operator*=(Lhs &lhs, Rhs const &rhs) noexcept
Inplace geometric translation.
Definition transform.hpp:436
constexpr matrix2 operator*(matrix2 const &lhs, matrix2 const &rhs) noexcept
Matrix/Matrix multiplication.
Definition transform.hpp:69
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
Definition decimal.hpp:20
constexpr int exponent() const noexcept
Extract exponent from value.
Definition decimal.hpp:200
constexpr long long mantissa() const noexcept
Extract mantissa from value.
Definition decimal.hpp:209
constexpr decimal normalize() const noexcept
Return a normalized decimal.
Definition decimal.hpp:224
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)