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