CeresEngine 0.2.0
A game development framework
Loading...
Searching...
No Matches
FixedPoint.Stream.hpp
Go to the documentation of this file.
1//
2// CeresEngine - A game development framework
3//
4// Created by Rogiel Sulzbach.
5// Copyright (c) 2018-2023 Rogiel Sulzbach. All rights reserved.
6//
7
8// Based on https://archive.is/Rx9HR
9
10#include "FixedPoint.hpp"
11#include "LargeInteger.hpp"
12
13#include <iostream>
14
15namespace CeresEngine {
16
17 // Returns the index of the most-signifcant set bit
18 template<typename T> inline long findHighestBit(T value) noexcept {
19 return sizeof(value) * 8 - 1 - LargeIntegerCLZHelper<T>::clz(value);
20 }
21
22 template<typename CharT, typename B, unsigned int F> std::basic_ostream<CharT>& operator<<(std::basic_ostream<CharT>& os, FixedPoint<B, F> x) noexcept {
23 const auto uppercase = ((os.flags() & std::ios_base::uppercase) != 0);
24 const auto showpoint = ((os.flags() & std::ios_base::showpoint) != 0);
25 const auto adjustfield = (os.flags() & std::ios_base::adjustfield);
26 const auto width = os.width();
27 const auto& ctype = std::use_facet<std::ctype<CharT>>(os.getloc());
28 const auto& numpunct = std::use_facet<std::numpunct<CharT>>(os.getloc());
29
30 auto floatfield = (os.flags() & std::ios_base::floatfield);
31 auto precision = os.precision();
32 auto showTrailingZeros = true;
33 auto useSignificantDigits = false;
34
35 // Invalid precision? Reset to the default
36 if(precision < 0) {
37 precision = 6;
38 }
39
40 // Output buffer. Needs to be big enough for the formatted number without padding.
41 // Optional prefixes (i.e. "+"/"-", decimal separator, exponent "e+/-" and/or "0x").
42 constexpr auto worstCaseConstantSize = 6;
43
44 // Maximum number of digits from the base type (covers integral + fractional digits)
45 constexpr auto worstCaseDigitCount = std::numeric_limits<B>::digits10 + 2;
46
47 // Exponent suffixes (i.e. maximum digits based on log of the base type size).
48 // Needs a log10, but that isn't constexpr, so we're over-allocating on the stack. Can't hurt.
49 constexpr auto worstCaseSuffixSize = std::numeric_limits<B>::digits;
50
51 // Double the digit count: in the worst case the thousands grouping add a character per digit.
52 using BufferType = std::array<CharT, worstCaseConstantSize + worstCaseDigitCount * 2 + worstCaseSuffixSize>;
53 BufferType buffer;
54
55 // Output cursor
56 auto end = buffer.begin();
57
58 // Keep track of the start of "internal" padding
59 typename BufferType::iterator internalPad = buffer.end();
60
61 // Representation of a number.
62 // The value of the number is: raw / divisor * (10|2) ^ exponent
63 // The base of the exponent is 2 in hexfloat mode, or 10 otherwise.
64 struct NumberType {
65 B raw; // raw fixed-point value
66 B divisor; // the divisor indicating the place of the decimal point
67 int exponent; // the exponent applied
68 };
69
70 // Convert a value without exponent to scientific representation
71 // where the part before the decimal point is less than 10.
72 const auto asScientific = [](NumberType value) {
73 assert(value.exponent == 0);
74 if(value.raw > 0) {
75 while(value.raw / 10 >= value.divisor) {
76 value.divisor *= 10;
77 ++value.exponent;
78 }
79 while(value.raw < value.divisor) {
80 value.raw *= 10;
81 --value.exponent;
82 }
83 }
84 return value;
85 };
86
87 NumberType value = {x.raw, B{1} << F, 0};
88
89 auto base = B{10};
90
91 // First write the sign
92 if(value.raw < 0) {
93 *end++ = ctype.widen('-');
94 value.raw = -value.raw;
96 } else if(os.flags() & std::ios_base::showpos) {
97 *end++ = ctype.widen('+');
99 }
100 assert(value.raw >= B(0));
101
102 switch(floatfield) {
103 case std::ios_base::fixed | std::ios_base::scientific:
104 // Hexadecimal mode: figure out the hexadecimal exponent and write "0x"
105 if(value.raw > 0) {
106 auto bit = findHighestBit(value.raw);
107 value.exponent = bit - F; // exponent is applied to base 2
108 value.divisor = B{1} << bit; // divisor is at the highest bit, ensuring it starts with "1."
109 precision = (bit + 3) / 4; // precision is number of nibbles, so we show all of them
110 }
111 base = 16;
112 showTrailingZeros = false; // Always strip trailing zeros in hexfloat mode
113
114 *end++ = ctype.widen('0');
115 *end++ = ctype.widen(uppercase ? 'X' : 'x');
116 break;
117
118 case std::ios_base::scientific:
119 // Scientific mode, normalize value to scientific notation
120 value = asScientific(value);
121 break;
122
123 case std::ios_base::fixed:
124 // Fixed mode. Nothing to do.
125 break;
126
127 default: {
128 // "auto" mode: figure out the exponent
130
131 // Now `precision` indicates the number of *significant digits* (not fractional digits).
133 precision = std::max<std::streamsize>(precision, 1);
134
135 if(scientificValue.exponent >= precision || scientificValue.exponent < -4) {
136 // Display as scientific format
137 floatfield = std::ios_base::scientific;
138 value = scientificValue;
139 } else {
140 // Display as fixed format.
141 // "showpoint" indicates whether or not we show trailing zeros
142 floatfield = std::ios_base::fixed;
144 }
145 break;
146 }
147 };
148
149 // If we didn't write a sign, any internal padding starts here
150 // (after a potential "0x" for hexfloats).
151 if(internalPad == buffer.end()) {
153 }
154
155 // Separate out the integral part of the number
156 B integral = value.raw / value.divisor;
157 value.raw %= value.divisor;
158
159 // Here we start printing the number itself
160 const char* const digits = uppercase ? "0123456789ABCDEF" : "0123456789abcdef";
161 const auto digitsStart = end;
162
163 // Are we already printing significant digits? (yes if we're not counting significant digits)
165
166 // Print the integral part
167 B lastDigit = 0;
168 if(integral == 0) {
169 *end++ = ctype.widen('0');
170 if(value.raw == 0) {
171 // If the fraction is zero too, all zeros including the integral count
172 // as significant digits.
173 significantDigits = true;
174 }
175 } else {
176 while(integral > 0) {
177 lastDigit = integral % base;
178 *end++ = ctype.widen(digits[(int)lastDigit]);
179 integral /= base;
180 }
181 std::reverse(digitsStart, end);
182 significantDigits = true;
183 }
184
186 // Apparently, the integral part was significant; subtract its
187 // length from the remaining significant digits.
189 }
190
191 // At this point, `value` contains only the fraction and
192 // `precision` holds the number of digits to print.
193 assert(value.raw < value.divisor);
194 assert(precision >= 0);
195
196 // Location of decimal point
197 typename BufferType::iterator point = buffer.end();
198
199 // Start (and length) of the trailing zeros to insert while printing
200 // By tracking this to print them later instead of actually printing them now,
201 // we can support large precisions with a small printing buffer.
202 typename BufferType::iterator trailingZerosStart = buffer.end();
203 std::streamsize trailingZerosCount = 0;
204
205 if(precision > 0) {
206 // Print the fractional part
207 *(point = end++) = numpunct.decimal_point();
208
209 for(int i = 0; i < precision; ++i) {
210 if(value.raw == 0) {
211 // The rest of the digits are all zeros, mark them
212 // to be printed in this spot.
215 break;
216 }
217
218 // Shift the divisor if we can to avoid overflow on the value
219 if(value.divisor % base == 0) {
220 value.divisor /= base;
221 } else {
222 value.raw *= base;
223 }
224 assert(value.divisor > 0);
225 assert(value.raw >= 0);
226 lastDigit = (value.raw / value.divisor) % base;
227 value.raw %= value.divisor;
228 *end++ = ctype.widen(digits[(int)lastDigit]);
229
230 if(!significantDigits) {
231 // We're still finding the first significant digit
232 if(lastDigit != 0) {
233 // Found it
234 significantDigits = true;
235 } else {
236 // Not yet; increment number of digits to print
237 ++precision;
238 }
239 }
240 }
241 } else if(showpoint) {
242 // No fractional part to print, but we still want the point
243 *(point = end++) = numpunct.decimal_point();
244 }
245
246 // Insert `ch` into the output at `position`, updating all references accordingly
247 const auto insertCharacter = [&](typename BufferType::iterator position, CharT ch) {
248 assert(position >= buffer.begin() && position < end);
249 std::move_backward(position, end, end + 1);
250 if(point != buffer.end() && position < point) {
251 ++point;
252 }
253 if(trailingZerosStart != buffer.end() && position < trailingZerosStart) {
255 }
256 ++end;
257 *position = ch;
258 };
259
260 // Round the number: round to nearest
261 bool increment = false;
262 if(value.raw > value.divisor / 2) {
263 // Round up
264 increment = true;
265 } else if(value.raw == value.divisor / 2) {
266 // It's a tie (i.e. "xyzw.5"): round to even
267 increment = ((lastDigit % 2) == 1);
268 }
269
270 if(increment) {
271 auto p = end - 1;
272 // Increment all digits backwards while we see "9"
273 while(p >= digitsStart) {
274 if(p == point) {
275 // Skip over the decimal point
276 --p;
277 }
278 if((*p)++ != ctype.widen('9')) {
279 break;
280 }
281 *p-- = ctype.widen('0');
282 }
283
284 if(p < digitsStart) {
285 // We've incremented all the way to the start (all 9's), we need to insert the
286 // carried-over 1 from incrementing the last 9.
287 assert(p == digitsStart - 1);
288 insertCharacter(++p, ctype.widen('1'));
289
290 if(floatfield == std::ios::scientific) {
291 // We just made the integral part equal to 10, so we shift the decimal point
292 // back one place (if any) and tweak the exponent, so that we keep the integer part
293 // less than 10.
294 if(point != buffer.end()) {
295 assert(p + 2 == point);
296 std::swap(*(point - 1), *point);
297 --point;
298 }
299 ++value.exponent;
300
301 // We've introduced an extra digit so we need to strip the last digit
302 // to maintain the same precision
303 --end;
304 }
305 }
306
307 if(useSignificantDigits && *p == ctype.widen('1') && point != buffer.end()) {
308 // We've converted a leading zero to a 1 so we need to strip the last digit
309 // (behind the decimal point) to maintain the same significant digit count.
310 --end;
311 }
312 }
313
314 if(point != buffer.end()) {
315 if(!showTrailingZeros) {
316 // Remove trailing zeros
317 while(*(end - 1) == ctype.widen('0')) {
318 --end;
319 }
320
321 // Also clear the "trailing zeros to append during printing" range
322 trailingZerosStart = buffer.end();
324 }
325
326 if(end - 1 == point && trailingZerosCount == 0 && !showpoint) {
327 // Remove the decimal point, too
328 --end;
329 }
330 }
331
332 // Apply thousands grouping
333 const auto& grouping = numpunct.grouping();
334 if(!grouping.empty()) {
335 // Step backwards from the end or decimal point, inserting the
336 // thousands separator at every group interval.
337 const CharT thousandsSeparator = ctype.widen(numpunct.thousands_sep());
338 std::size_t group = 0;
339 auto p = point != buffer.end() ? point : end;
340 auto size = static_cast<int>(grouping[group]);
341 while(size > 0 && size < CHAR_MAX && p - digitsStart > size) {
342 p -= size;
344 if(group < grouping.size() - 1) {
345 size = static_cast<int>(grouping[++group]);
346 }
347 }
348 }
349
350 // Print the exponent if required
351 assert(floatfield != 0);
352 if(floatfield & std::ios_base::scientific) {
353 // Hexadecimal (%a/%A) or decimal (%e/%E) scientific notation
354 if(floatfield & std::ios_base::fixed) {
355 *end++ = ctype.widen(uppercase ? 'P' : 'p');
356 } else {
357 *end++ = ctype.widen(uppercase ? 'E' : 'e');
358 }
359
360 if(value.exponent < 0) {
361 *end++ = ctype.widen('-');
362 value.exponent = -value.exponent;
363 } else {
364 *end++ = ctype.widen('+');
365 }
366
367 if(floatfield == std::ios_base::scientific) {
368 // In decimal scientific notation (%e/%E), the exponent is at least two digits
369 if(value.exponent < 10) {
370 *end++ = ctype.widen('0');
371 }
372 }
373
374 const auto exponent_start = end;
375 if(value.exponent == 0) {
376 *end++ = ctype.widen('0');
377 } else
378 while(value.exponent > 0) {
379 *end++ = ctype.widen(digits[value.exponent % 10]);
380 value.exponent /= 10;
381 }
382 std::reverse(exponent_start, end);
383 }
384
385 // Write character `ch` `count` times to the stream
386 const auto putCharacter = [&](CharT ch, const std::streamsize count) {
387 // Fill a buffer to output larger chunks
388 constexpr std::streamsize chunkSize = 64;
389 std::array<CharT, chunkSize> fillBuffer;
390 std::fill_n(fillBuffer.begin(), std::min(count, chunkSize), ch);
391
392 for(std::streamsize size, left = count; left > 0; left -= size) {
393 size = std::min(chunkSize, left);
394 os.rdbuf()->sputn(&fillBuffer[0], size);
395 }
396 };
397
398 // Outputs a range of characters, making sure to output the trailing zeros range
399 // if it lies in the specified range
400 const auto putRange = [&](typename BufferType::const_iterator begin, typename BufferType::const_iterator end) {
401 assert(end >= begin);
402 if(trailingZerosStart >= begin && trailingZerosStart <= end) {
403 // Print range with trailing zeros range in the middle
405 os.rdbuf()->sputn(&*begin, trailingZerosStart - begin);
407 os.rdbuf()->sputn(&*trailingZerosStart, end - trailingZerosStart);
408 } else {
409 // Print range as-is
410 os.rdbuf()->sputn(&*begin, end - begin);
411 }
412 };
413
414 // Pad the buffer if necessary.
415 // Note that the length of trailing zeros is counted towards the length of the content.
416 const auto contentSize = end - buffer.begin() + trailingZerosCount;
417 if(contentSize >= width) {
418 // Buffer needs no padding, output as-is
419 putRange(buffer.begin(), end);
420 } else {
421 const auto padSize = width - contentSize;
422 switch(adjustfield) {
423 case std::ios_base::left:
424 // Content is left-aligned, so output the buffer, followed by the padding
425 putRange(buffer.begin(), end);
426 putCharacter(os.fill(), padSize);
427 break;
428 case std::ios_base::internal:
429 // Content is internally aligned, so output the buffer up to the "internal pad"
430 // point, followed by the padding, followed by the remainder of the buffer.
431 putRange(buffer.begin(), internalPad);
432 putCharacter(os.fill(), padSize);
433 putRange(internalPad, end);
434 break;
435 default:
436 // Content is right-aligned, so output the padding, followed by the buffer
437 putCharacter(os.fill(), padSize);
438 putRange(buffer.begin(), end);
439 break;
440 }
441 }
442
443 // Width is reset after every write
444 os.width(0);
445
446 return os;
447 }
448
449 template<typename CharT, class Traits, typename B, unsigned int F>
450 std::basic_istream<CharT, Traits>& operator>>(std::basic_istream<CharT, Traits>& is, FixedPoint<B, F>& x) {
451 const typename std::basic_istream<CharT, Traits>::sentry sentry(is);
452 if(!sentry) {
453 return is;
454 }
455
456 const auto& ctype = std::use_facet<std::ctype<CharT>>(is.getloc());
457 const auto& numpunct = std::use_facet<std::numpunct<CharT>>(is.getloc());
458
459 bool thousandsSeparatorAllowed = false;
460 const bool supportsThousandsSeparators = !numpunct.grouping().empty();
461
462 const auto& isValidCharacter = [](const char ch) {
463 // Note: allowing ['p', 'i', 'n', 't', 'y'] is technically in violation of the spec (we are emulating std::num_get),
464 // but otherwise we cannot parse hexfloats and "infinity". This is a known issue with the spec (LWG #2381).
465 return std::isxdigit(ch) || ch == 'x' || ch == 'X' || ch == 'p' || ch == 'P' || ch == 'i' || ch == 'I' || ch == 'n' || ch == 'N' || ch == 't' ||
466 ch == 'T' || ch == 'y' || ch == 'Y' || ch == '-' || ch == '+';
467 };
468
469 const auto& peek = [&]() {
470 for(;;) {
471 auto ch = is.rdbuf()->sgetc();
472 if(ch == Traits::eof()) {
473 is.setstate(std::ios::eofbit);
474 return '\0';
475 }
476 if(ch == numpunct.decimal_point()) {
477 return '.';
478 }
479 if(ch == numpunct.thousands_sep()) {
481 return '\0';
482 }
483 // Ignore valid thousands separators
484 is.rdbuf()->sbumpc();
485 continue;
486 }
487 auto res = ctype.narrow(ch, 0);
488 if(!isValidCharacter(res)) {
489 // Invalid character: end input
490 return '\0';
491 }
492 return res;
493 }
494 };
495
496 const auto& bump = [&]() {
497 is.rdbuf()->sbumpc();
498 };
499
500 const auto& next = [&]() {
501 bump();
502 return peek();
503 };
504
505 bool negate = false;
506 auto ch = peek();
507 if(ch == '-') {
508 negate = true;
509 ch = next();
510 } else if(ch == '+') {
511 ch = next();
512 }
513
514 const char infinity[] = "infinity";
515 // Must be "inf" or "infinity"
516 int index = 0;
517 while(index < 8 && ch == infinity[index]) {
518 ++index;
519 ch = next();
520 }
521
522 if(index > 0) {
523 if(index == 3 || index == 8) {
524 x = negate ? std::numeric_limits<FixedPoint<B, F>>::min() : std::numeric_limits<FixedPoint<B, F>>::max();
525 } else {
526 is.setstate(std::ios::failbit);
527 }
528 return is;
529 }
530
531 char exponentChar = 'e';
532 int base = 10;
533
534 constexpr auto noFraction = std::numeric_limits<std::size_t>::max();
535 std::size_t fractionStart = noFraction;
536 std::vector<unsigned char> significand;
537
538 if(ch == '0') {
539 ch = next();
540 if(ch == 'x' || ch == 'X') {
541 // Hexfloat
542 exponentChar = 'p';
543 base = 16;
544 ch = next();
545 } else {
546 significand.push_back(0);
547 }
548 }
549
550 // Parse the significand
552 for(;; ch = next()) {
553 if(ch == '.') {
555 // Double decimal point. Stop parsing.
556 break;
557 }
558 fractionStart = significand.size();
560 } else {
561 unsigned char val = base;
562 if(ch >= '0' && ch <= '9') {
563 val = ch - '0';
564 } else if(ch >= 'a' && ch <= 'f') {
565 val = ch - 'a' + 10;
566 } else if(ch >= 'A' && ch <= 'F') {
567 val = ch - 'A' + 10;
568 }
569 if(val < 0 || val >= base) {
570 break;
571 }
572 significand.push_back(val);
573 }
574 }
575 if(significand.empty()) {
576 // We need a significand
577 is.setstate(std::ios::failbit);
578 return is;
579 }
581
583 // If we haven't seen a fraction yet, place it at the end of the significand
584 fractionStart = significand.size();
585 }
586
587 // Parse the exponent
588 bool exponentOverflow = false;
589 std::size_t exponent = 0;
590 bool exponentNegate = false;
591 if(std::tolower(ch) == exponentChar) {
592 ch = next();
593 if(ch == '-') {
594 exponentNegate = true;
595 ch = next();
596 } else if(ch == '+') {
597 ch = next();
598 }
599
600 bool parsed = false;
601 while(std::isdigit(ch)) {
602 if(exponent <= std::numeric_limits<int>::max() / 10) {
603 exponent = exponent * 10 + (ch - '0');
604 } else {
605 exponentOverflow = true;
606 }
607 parsed = true;
608 ch = next();
609 }
610 if(!parsed) {
611 // If the exponent character is given, the exponent value may not be empty
612 is.setstate(std::ios::failbit);
613 return is;
614 }
615 }
616
617 // We've parsed all we need. Construct the value.
618 if(exponentOverflow) {
619 // Absolute exponent is too large
620 if(std::all_of(significand.begin(), significand.end(), [](const unsigned char x) { return x == 0; })) {
621 // Significand is zero. Exponent doesn't matter.
622 x = FixedPoint<B, F>(B(0));
623 } else if(exponentNegate) {
624 // A huge negative exponent approaches 0.
625 x = FixedPoint<B, F>(B(0));
626 } else {
627 // A huge positive exponent approaches infinity.
628 x = std::numeric_limits<FixedPoint<B, F>>::max();
629 }
630 return is;
631 }
632
633 // Shift the fraction offset according to exponent
634 {
635 const auto exponentMultiplier = (base == 10) ? 1 : 4;
636 if(exponentNegate) {
637 const auto adjust = std::min(exponent / exponentMultiplier, fractionStart);
640 } else {
641 const auto adjust = std::min(exponent / exponentMultiplier, significand.size() - fractionStart);
644 }
645 }
646
647 constexpr auto isSigned = std::is_signed<B>::value;
648 constexpr auto intBits = sizeof(B) * 8 - F - (isSigned ? 1 : 0);
649 constexpr auto maxInt = (B{1} << intBits) - 1;
650 constexpr auto maxFraction = (B{1} << F) - 1;
651 constexpr auto maxValue = (B{1} << sizeof(B) * 8) - 1;
652
653 // Parse the integer part
654 B integer = 0;
655 for(std::size_t i = 0; i < fractionStart; ++i) {
656 if(integer > maxInt / base) {
657 // Overflow
658 x = negate ? std::numeric_limits<FixedPoint<B, F>>::min() : std::numeric_limits<FixedPoint<B, F>>::max();
659 return is;
660 }
661 assert(significand[i] < base);
662 integer = integer * base + significand[i];
663 }
664
665 // Parse the fractional part
666 B fraction = 0;
667 B divisor = 1;
668 for(std::size_t i = fractionStart; i < significand.size(); ++i) {
669 assert(significand[i] < base);
670 if(divisor > maxFraction / base) {
671 // We're done
672 break;
673 }
674 fraction = fraction * base + significand[i];
675 divisor *= base;
676 }
677
678 // Construct the value from the parsed parts
679 B rawValue = (integer << F) + (fraction << F) / divisor;
680
681 // Apply remaining exponent
682 if(exponentChar == 'p') {
683 // Base-2 exponent
684 if(exponentNegate) {
685 rawValue >>= exponent;
686 } else {
687 rawValue <<= exponent;
688 }
689 } else {
690 // Base-10 exponent
691 if(exponentNegate) {
692 B remainder = 0;
693 for(std::size_t e = 0; e < exponent; ++e) {
694 remainder = rawValue % 10;
695 rawValue /= 10;
696 }
697 rawValue += remainder / 5;
698 } else {
699 for(std::size_t e = 0; e < exponent; ++e) {
700 if(rawValue > maxValue / 10) {
701 // Overflow
702 x = negate ? std::numeric_limits<FixedPoint<B, F>>::min() : std::numeric_limits<FixedPoint<B, F>>::max();
703 return is;
704 }
705 rawValue *= 10;
706 }
707 }
708 }
709 x = FixedPoint<B, F>(static_cast<B>(negate ? -rawValue : rawValue));
710
711 return is;
712 }
713
714} // namespace CeresEngine
Iterator< Generator > end(const Generator &) noexcept
Returns a dummy end iterator.
Definition Iterator.hpp:82
Iterator< Generator > begin(Generator &generator) noexcept
Will return an iterator to the generator.
Definition Iterator.hpp:79
BufferType
Definition ASTEnums.hpp:492
Definition Application.hpp:19
constexpr Byte operator>>(const Byte arg, const _IntType shift) noexcept
Definition DataTypes.hpp:51
long findHighestBit(T value) noexcept
Definition FixedPoint.Stream.hpp:18
constexpr Byte operator<<(const Byte arg, const _IntType shift) noexcept
Definition DataTypes.hpp:44
constexpr CountAlgorithmFunctor count
Returns the number of elements matching an element.
Definition Count.hpp:82
constexpr size_t hash(const T &v)
Generates a hash for the provided type.
Definition Hash.hpp:25