NDEVR
API Documentation
NumberWriter.h
1/*--------------------------------------------------------------------------------------------
2Copyright (c) 2019, NDEVR LLC
3tyler.parke@ndevr.org
4 __ __ ____ _____ __ __ _______
5 | \ | | | __ \ | ___|\ \ / / | __ \
6 | \ | | | | \ \ | |___ \ \ / / | |__) |
7 | . \| | | |__/ / | |___ \ V / | _ /
8 | |\ |_|_____/__|_____|___\_/____| | \ \
9 |__| \__________________________________| \__\
10
11Subject to the terms of the Enterprise+ Agreement, NDEVR hereby grants
12Licensee a limited, non-exclusive, non-transferable, royalty-free license
13(without the right to sublicense) to use the API solely for the purpose of
14Licensee's internal development efforts to develop applications for which
15the API was provided.
16
17The above copyright notice and this permission notice shall be included in all
18copies or substantial portions of the Software.
19
20THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
21INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
22PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
23FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
24OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25DEALINGS IN THE SOFTWARE.
26
27Library: Base
28File: NumberWriter
29Included in API: True
30Author(s): Tyler Parke
31 *-----------------------------------------------------------------------------------------**/
32#pragma once
33#include <NDEVR/String.h>
34#include <NDEVR/BaseValues.h>
35#include <NDEVR/NumberParser.h>
36#include <cmath>
37
38namespace NDEVR
39{
41 static constexpr fltp08 kPowersOfTen[23] =
42 {
43 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
44 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
45 1e20, 1e21, 1e22
46 };
47
52 {
53 private:
59 static inline fltp08 QuickPowTen(uint04 exp) noexcept
60 {
61 if (exp < 23)
62 return kPowersOfTen[exp];
63 else
64 return cast<fltp08>(std::pow(10, exp));
65 }
69 class D2A
70 {
71 static constexpr fltp08 kLog2_10 = 0.30102999566398119521373889472449;
72 static constexpr uint04 maxBase2Precision = 53;
73 static constexpr uint08 two_pow_maxBase2PrecisionMinus1 = uint08(1) << (maxBase2Precision - 1);
74
80 static inline fltp08 quickPowTwo(uint04 exp)
81 {
82 if (0 < exp && exp < 64)
83 return cast<fltp08>(uint64_t(1) << exp);
84 else
85 return cast<fltp08>(std::pow(2, exp));
86 }
87
88 public:
95 D2A(const fltp08 avalue, bool fixedPrecision, uint04 minPrecision)
96 : value(avalue)
97 , e(0)
98 , finished(false)
99 {
100 mantissa = n_frexp(value, &e);
101
102 if (fixedPrecision)
103 {
104 lowOk = highOk = true;
105 }
106 else
107 {
108 bool round = (mantissa & 1) == 0;
109 lowOk = highOk = round;
110 }
111
112 mantissaPrec = maxBase2Precision;
113 while (mantissaPrec != 0 && (((mantissa >> --mantissaPrec) & 1) == 0)) {}
114 mantissaPrec++;
115 if (e >= 0)
116 {
117 if (mantissa != two_pow_maxBase2PrecisionMinus1)
118 {
119 const fltp08 be = quickPowTwo(e);
120
121 dr = cast<fltp08>(mantissa) * be * 2;
122 ds = 2;
123 dMPlus = be;
124 dMMinus = be;
125 }
126 else
127 {
128 fltp08 be = quickPowTwo(e);
129 fltp08 be1 = be * 2;
130
131 dr = cast<fltp08>(mantissa) * be1 * 2;
132 ds = 4;
133 dMPlus = be1;
134 dMMinus = be;
135 }
136 }
137 else if (mantissa != two_pow_maxBase2PrecisionMinus1)
138 {
139
140 dr = cast<fltp08>(mantissa) * 2.0;
141 ds = quickPowTwo(1 - e); // pow(2,-e)*2;
142 dMPlus = 1;
143 dMMinus = 1;
144 }
145 else
146 {
147 dr = cast<fltp08>(mantissa) * 4.0;
148 ds = quickPowTwo(2 - e); // pow(2, 1-e)*2;
149 dMPlus = 2;
150 dMMinus = 1;
151 }
152 if (fixedPrecision)
153 {
154 const fltp08 fixedPrecisionPowTen = QuickPowTen(minPrecision);
155 ds *= fixedPrecisionPowTen;
156 dr *= fixedPrecisionPowTen;
157 }
158 base10Exp = scale();
159 }
160
167 uint08 n_frexp(double v, int* eptr) const
168 {
169 fltp08 fracMantissa = std::frexp(v, eptr);
170 *eptr -= 53; // 52 mantissa bits + the hidden bit
171 return (uint64_t)(fracMantissa * (double)(1LL << 53));
172 }
173
178 uint01 nextDigit()
179 {
180 if (finished)
181 return Constant<uint01>::Invalid;
182
183 bool withinLowEndRoundRange;
184 bool withinHighEndRoundRange;
185 uint01 quotient;
186
187 quotient = cast<uint01>(dr / ds);
188 lib_assert(quotient < 10, "quotient cannot be > 9");
189 double mod = fmod(dr, ds);
190 dr = mod;
191
192 // check if remaining ratio r/s is within the range of floats which would round to the value we have output
193 // so far when read in from a string.
194 withinLowEndRoundRange = (lowOk ? (dr <= dMMinus) : (dr < dMMinus));
195 withinHighEndRoundRange = (highOk ? (dr + dMPlus >= ds) : (dr + dMPlus > ds));
196
197
198 if (!withinLowEndRoundRange)
199 {
200 if (!withinHighEndRoundRange) // if not within either error range, set up to generate the next digit.
201 {
202 dr *= 10;
203 dMPlus *= 10;
204 dMMinus *= 10;
205 }
206 else
207 {
208 quotient++;
209 finished = true;
210 }
211 }
212 else if (!withinHighEndRoundRange)
213 {
214 finished = true;
215 }
216 else
217 {
218 if (dr * 2 < ds) // if (r*2 < s) todo: faster to do lshift and compare?
219 {
220 finished = true;
221 }
222 else
223 {
224 quotient++;
225 finished = true;
226 }
227 }
228 return quotient;
229 }
230
235 sint04 expBase10() { return base10Exp; }
236
237 fltp08 value; ///< The original double value being converted.
238 sint04 e; ///< The binary exponent from decomposition.
239 uint08 mantissa; ///< On input, value = mantissa*2^e; Only last 53 bits are used.
240 sint04 mantissaPrec; ///< The number of bits of precision that are present in the mantissa.
241 sint04 base10Exp; ///< The (derived) base 10 exponent of value.
242 bool finished; ///< Set to true when we've output all relevant digits.
243
244 private:
245 bool lowOk; ///< For IEEE unbiased rounding, this is true when mantissa is even. When true, use >= in mMinus test instead of >
246 bool highOk; ///< Ditto, but for mPlus test.
247
248 // If !bFastEstimateOk, use these.
249
250 // If bFastEstimateOk, use these - same as above, but integer value stored in double.
251 fltp08 dr; ///< Scaled remainder value used during digit generation.
252 fltp08 ds; ///< Scaled divisor value used during digit generation.
253 fltp08 dMPlus; ///< Upper error bound for rounding during digit generation.
254 fltp08 dMMinus; ///< Lower error bound for rounding during digit generation.
255
256 // Estimate base 10 exponent of number, scale r,s,mPlus,mMinus appropriately.
257 // Returns result of fixup_ExponentEstimate(est).
262 sint04 scale()
263 {
264 // estimate base10 exponent:
265 sint04 base2Exponent = e + mantissaPrec - 1;
266 sint04 exponentEstimate = (sint04)ceil((base2Exponent * kLog2_10) - 0.0000000001);
267
268 fltp08 scale = QuickPowTen((exponentEstimate > 0) ? exponentEstimate : -exponentEstimate);
269
270 if (exponentEstimate >= 0)
271 {
272 ds *= scale;
273 return fixup_ExponentEstimate(exponentEstimate);
274 }
275 else
276 {
277 dr *= scale;
278 dMPlus *= scale;
279 dMMinus *= scale;
280 return fixup_ExponentEstimate(exponentEstimate);
281 }
282 }
283
284 // Used by scale to adjust for possible off-by-one error in the base 10 exponent estimate.
285 // Returns exact base10 exponent of number.
291 sint04 fixup_ExponentEstimate(sint04 exponentEstimate)
292 {
293 sint04 correctedEstimate;
294 uint01 quotient = cast<uint01>((dr * 10) / ds);
295 if ((highOk ? (dr + dMPlus) >= ds : dr + dMPlus > ds) || quotient >= 10)
296 {
297 correctedEstimate = exponentEstimate + 1;
298 }
299 else
300 {
301 dr *= 10;
302 dMPlus *= 10;
303 dMMinus *= 10;
304 correctedEstimate = exponentEstimate;
305 }
306 quotient = cast<uint01>(dr / ds);
307 lib_assert(quotient < 10, "quotient cannot be > 9");
308 return correctedEstimate;
309 }
310 };
311 public:
315 enum flt_to_string {
316 DTOSTR_NORMAL, ///< Default formatting, choosing the shortest accurate representation.
317 DTOSTR_FIXED, ///< Fixed-point notation with a specified number of decimal places.
318 DTOSTR_PRECISION, ///< Precision mode with a specified number of significant digits.
319 DTOSTR_EXPONENTIAL ///< Scientific/exponential notation (e.g. 1.23e+4).
320 };
321
327 template<class t_type>
328 constexpr static void writeInt(String& string, t_type initial_value)
329 {
330 t_type value = initial_value;
331 constexpr uint04 max_digits = MaxDigits<t_type>() + (ObjectInfo<t_type>::Unsigned ? 0 : 1U);
332 char digits[max_digits];
333 uint04 index = max_digits;
334 if constexpr (!ObjectInfo<t_type>::Unsigned)
335 {
336 if (initial_value < cast<t_type>(0))
337 value = cast<t_type>(0) - value;
338 }
339 if (value == 0)
340 {
341 string.add('0');
342 return;
343 }
344 while (index != 0)
345 {
346 t_type j = value;
347 value = value / cast<t_type>(10);
348 j -= (value * cast<t_type>(10));
349 digits[--index] = (cast<char>(j) + '0');
350 if (value == cast<t_type>(0))
351 break;
352 }
353 if constexpr ((!ObjectInfo<t_type>::Unsigned))
354 {
355 if(initial_value < cast<t_type>(0))
356 digits[--index] = '-';
357 }
358 string.addAll(&digits[index], max_digits - index);
359 }
360
366 template<class t_type>
367 constexpr static void writeHex(String& string, t_type initial_value)
368 {
369 t_type value = initial_value;
370 constexpr uint04 max_digits = MaxDigits<t_type>() + (ObjectInfo<t_type>::Unsigned ? 0 : 1U);
371 char digits[max_digits];
372 uint04 index = max_digits;
373 if ((!ObjectInfo<t_type>::Unsigned) && initial_value < cast<t_type>(0))
374 {
375 value = cast<t_type>(0) - value;
376 }
377 if (value == 0)
378 {
379 string.add('0');
380 return;
381 }
382 while (index != 0)
383 {
384 t_type j = value;
385 value = value / cast<t_type>(16);
386 j -= (value * cast<t_type>(16));
387
388 if (j < 10)
389 digits[--index] = '0' + cast<char>(j);
390 else
391 {
392 switch (j)
393 {
394 case 10: digits[--index] = 'A'; break;
395 case 11: digits[--index] = 'B'; break;
396 case 12: digits[--index] = 'C'; break;
397 case 13: digits[--index] = 'D'; break;
398 case 14: digits[--index] = 'E'; break;
399 case 15: digits[--index] = 'F'; break;
400 }
401 }
402 if (value == cast<t_type>(0))
403 break;
404 }
405 string.addAll(&digits[index], max_digits - index);
406 }
407
415 static void writeFloat(String& string, fltp08 value, flt_to_string mode = DTOSTR_NORMAL, uint01 precision = 10)
416 {
417 if (value > Constant<fltp08>::Max)
418 {
419 string.addAll("-Infinity");
420 return;
421 }
422 if (value < Constant<fltp08>::Min)
423 {
424 string.addAll("Infinity");
425 return;
426 }
427 if (IsInvalid(value))
428 {
429 string.addAll("NaN");
430 return;
431 }
432 if (mode == DTOSTR_NORMAL)
433 {
434 sint08 intValue = cast<sint08>(value);
435 if ((value == (double)(intValue)) && ((uint32_t)intValue != 0x80000000))
436 {
437 writeInt(string, intValue);
438 return;
439 }
440 }
441 const bool negative = value < 0.0;
442 const bool zero = value == 0.0;
443 if (negative) {
444 value = -value;
445 string.add('-');
446 }
447 D2A d2a = D2A(value, mode != DTOSTR_NORMAL, precision);
448 int32_t exp10 = d2a.expBase10() - 1;
449
453 enum FormatType {
454 kNormal, ///< Standard decimal notation.
455 kExponential, ///< Scientific notation with exponent.
456 kFraction, ///< Fractional form (0.xxxx).
457 kFixedFraction ///< Fixed fractional form with specified decimal places.
458 };
459 FormatType format;
460
461 switch (mode) {
462 case DTOSTR_FIXED:
463 {
464 if (exp10 < 0) {
465 format = kFixedFraction;
466 }
467 else {
468 format = kNormal;
469 precision++;
470 }
471 }
472 break;
473 case DTOSTR_PRECISION:
474 {
475 if (exp10 < 0) {
476 format = kFraction;
477 }
478 else if (exp10 >= precision) {
479 format = kExponential;
480 }
481 else {
482 format = kNormal;
483 }
484 }
485 break;
486 case DTOSTR_EXPONENTIAL:
487 format = kExponential;
488 precision++;
489 break;
490 default:
491 if (exp10 < 0 && exp10 > -7) {
492 // Number is of form 0.######
493 if (exp10 < -precision) {
494 exp10 = -precision - 1;
495 }
496 format = kFraction;
497 }
498 else if (std::abs(exp10) > 20) { // ECMA spec 9.8.1
499 format = kExponential;
500 }
501 else {
502 format = kNormal;
503 }
504 }
505
506 // Digit generation.
507 switch (format) {
508 case kNormal:
509 {
510 int32_t digits = 0;
511 int32_t digit;
512 digit = d2a.nextDigit();
513 if (digit > 0)
514 string.add((char)(digit + '0'));
515 while (exp10 > 0)
516 {
517 digit = (d2a.finished) ? 0 : d2a.nextDigit();
518 string.add((char)(digit + '0'));
519 exp10--;
520 digits++;
521 }
522 if (mode == DTOSTR_FIXED)
523 {
524 digits = 0;
525 }
526 if (mode == DTOSTR_NORMAL)
527 {
528 if (!d2a.finished)
529 {
530 string.add('.');
531 while (!d2a.finished)
532 string.add((char)(d2a.nextDigit() + '0'));
533 }
534 }
535 else if (digits < precision - 1)
536 {
537 string.add('.');
538 for (; digits < precision - 1; digits++)
539 {
540 digit = d2a.finished ? 0 : d2a.nextDigit();
541 string.add((char)(digit + '0'));
542 }
543 }
544 }
545 break;
546 case kFixedFraction:
547 {
548 string.add('0'); // Sentinel
549 string.add('.');
550 // Write out leading zeroes
551 int32_t digits = 0;
552 if (exp10 > 0) {
553 while (++exp10 < 10 && digits < precision) {
554 string.add('0');
555 digits++;
556 }
557 }
558 else if (exp10 < 0) {
559 while ((++exp10 < 0) && (precision-- > 0))
560 string.add('0');
561 }
562 if (IsValid(precision))
563 {
564 // Write out significand
565 for (; digits < precision; digits++) {
566 if (d2a.finished)
567 {
568 if (mode == DTOSTR_NORMAL)
569 break;
570 string.add('0');
571 }
572 else {
573 string.add((char)(d2a.nextDigit() + '0'));
574 }
575 }
576 }
577 exp10 = 0;
578 }
579 break;
580 case kFraction:
581 {
582 string.add('0'); // Sentinel
583 string.add('.');
584
585 // Write out leading zeros
586 if (!zero)
587 {
588 for (int32_t i = exp10; i < -1; i++) {
589 string.add('0');
590 }
591 }
592
593 // Write out significand
594 int32_t i = 0;
595 while (!d2a.finished)
596 {
597 string.add((char)(d2a.nextDigit() + '0'));
598 if (mode != DTOSTR_NORMAL && ++i >= precision)
599 break;
600 }
601 if (mode == DTOSTR_PRECISION)
602 {
603 while (i++ < precision)
604 string.add((char)(d2a.finished ? '0' : d2a.nextDigit() + '0'));
605 }
606 exp10 = 0;
607 }
608 break;
609 case kExponential:
610 {
611 int32_t digit;
612 digit = d2a.finished ? 0 : d2a.nextDigit();
613 string.add((char)(digit + '0'));
614 if (((mode == DTOSTR_NORMAL) && !d2a.finished) ||
615 ((mode != DTOSTR_NORMAL) && precision > 1)) {
616 string.add('.');
617 for (int32_t i = 0; i < precision - 1; i++) {
618 if (d2a.finished)
619 {
620 if (mode == DTOSTR_NORMAL)
621 break;
622 string.add('0');
623 }
624 else {
625 string.add((char)(d2a.nextDigit() + '0'));
626 }
627 }
628 }
629 }
630 break;
631 }
632 // Clean up zeroes, and place the exponent
633 if (exp10)
634 {
635 if (!zero)
636 {
637 // Remove trailing zeroes, as might appear in 10e+95
638 while (string.size() > 0 && string.last() == '0')
639 {
640 string.removeLast();
641 exp10++;
642 }
643 }
644
645 // Place the exponent
646 string.add('e');
647 if (exp10 > 0) {
648 string.add('+');
649 }
650 writeInt(string, exp10);
651 }
652 }
653 };
654}
Logic for writing to a number.
The primary namespace for the NDEVR SDK.
uint64_t uint08
-Defines an alias representing an 8 byte, unsigned integer
uint32_t uint04
-Defines an alias representing a 4 byte, unsigned integer -Can represent exact integer values 0 throu...
double fltp08
Defines an alias representing an 8 byte floating-point number.
uint8_t uint01
-Defines an alias representing a 1 byte, unsigned integer -Can represent exact integer values 0 throu...
static constexpr fltp08 kPowersOfTen[23]
< Precomputed powers of ten from 1e0 to 1e22, used for fast exponentiation without calling std::pow.
constexpr t_to cast(const Angle< t_from > &value)
Casts an Angle from one backing type to another.
Definition Angle.h:408