API Documentation
Loading...
Searching...
No Matches
NumberWriter.h
Go to the documentation of this file.
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{
40 static const fltp08 kPowersOfTen[23] =
41 {
42 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
43 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
44 1e20, 1e21, 1e22
45 };
46 /**--------------------------------------------------------------------------------------------------
47 \brief Logic for writing to a number
48 **/
50 {
51 private:
52
53 static inline fltp08 quickPowTen(uint04 exp)
54 {
55 if (exp < 23)
56 return kPowersOfTen[exp];
57 else
58 return cast<fltp08>(std::pow(10, exp));
59 }
60 /**--------------------------------------------------------------------------------------------------
61 \brief Logic for writing to a number
62 **/
63 class D2A
64 {
65 static constexpr fltp08 kLog2_10 = 0.30102999566398119521373889472449;
66 static constexpr uint04 maxBase2Precision = 53;
67 static constexpr uint08 two_pow_maxBase2PrecisionMinus1 = uint08(1) << (maxBase2Precision - 1);
68 static inline fltp08 quickPowTwo(uint04 exp)
69 {
70 if (0 < exp && exp < 64)
71 return cast<fltp08>(uint64_t(1) << exp);
72 else
73 return cast<fltp08>(std::pow(2, exp));
74 }
75
76 public:
77 D2A(const fltp08 avalue, bool fixedPrecision, uint04 minPrecision)
78 : value(avalue)
79 , e(0)
80 , finished(false)
81 {
82 mantissa = n_frexp(value, &e);
83
84 if (fixedPrecision)
85 {
86 lowOk = highOk = true;
87 }
88 else
89 {
90 bool round = (mantissa & 1) == 0;
91 lowOk = highOk = round;
92 }
93
94 mantissaPrec = maxBase2Precision;
95 while (mantissaPrec != 0 && (((mantissa >> --mantissaPrec) & 1) == 0)) {}
96 mantissaPrec++;
97 if (e >= 0)
98 {
99 if (mantissa != two_pow_maxBase2PrecisionMinus1)
100 {
101 const fltp08 be = quickPowTwo(e);
102
103 dr = cast<fltp08>(mantissa) * be * 2;
104 ds = 2;
105 dMPlus = be;
106 dMMinus = be;
107 }
108 else
109 {
110 fltp08 be = quickPowTwo(e);
111 fltp08 be1 = be * 2;
112
113 dr = cast<fltp08>(mantissa) * be1 * 2;
114 ds = 4;
115 dMPlus = be1;
116 dMMinus = be;
117 }
118 }
119 else if (mantissa != two_pow_maxBase2PrecisionMinus1)
120 {
121
122 dr = cast<fltp08>(mantissa) * 2.0;
123 ds = quickPowTwo(1 - e); // pow(2,-e)*2;
124 dMPlus = 1;
125 dMMinus = 1;
126 }
127 else
128 {
129 dr = cast<fltp08>(mantissa) * 4.0;
130 ds = quickPowTwo(2 - e); // pow(2, 1-e)*2;
131 dMPlus = 2;
132 dMMinus = 1;
133 }
134 if (fixedPrecision)
135 {
136 const fltp08 fixedPrecisionPowTen = quickPowTen(minPrecision);
137 ds *= fixedPrecisionPowTen;
138 dr *= fixedPrecisionPowTen;
139 }
140 base10Exp = scale();
141 }
142 uint08 n_frexp(double v, int* eptr) const
143 {
144 fltp08 fracMantissa = std::frexp(v, eptr);
145 *eptr -= 53; // 52 mantissa bits + the hidden bit
146 return (uint64_t)(fracMantissa * (double)(1LL << 53));
147 }
148 uint01 nextDigit()
149 {
150 if (finished)
152
153 bool withinLowEndRoundRange;
154 bool withinHighEndRoundRange;
155 uint01 quotient;
156
157 quotient = cast<uint01>(dr / ds);
158 lib_assert(quotient < 10, "quotient cannot be > 9");
159 double mod = fmod(dr, ds);
160 dr = mod;
161
162 // check if remaining ratio r/s is within the range of floats which would round to the value we have output
163 // so far when read in from a string.
164 withinLowEndRoundRange = (lowOk ? (dr <= dMMinus) : (dr < dMMinus));
165 withinHighEndRoundRange = (highOk ? (dr + dMPlus >= ds) : (dr + dMPlus > ds));
166
167
168 if (!withinLowEndRoundRange)
169 {
170 if (!withinHighEndRoundRange) // if not within either error range, set up to generate the next digit.
171 {
172 dr *= 10;
173 dMPlus *= 10;
174 dMMinus *= 10;
175 }
176 else
177 {
178 quotient++;
179 finished = true;
180 }
181 }
182 else if (!withinHighEndRoundRange)
183 {
184 finished = true;
185 }
186 else
187 {
188 if (dr * 2 < ds) // if (r*2 < s) todo: faster to do lshift and compare?
189 {
190 finished = true;
191 }
192 else
193 {
194 quotient++;
195 finished = true;
196 }
197 }
198 return quotient;
199 }
200 sint04 expBase10() { return base10Exp; }
201
202 fltp08 value; // Double value for quick work when e and mantissa are small.
203 sint04 e;
204 uint08 mantissa; // On input, value = mantissa*2^e; Only last 53 bits are used.
205 sint04 mantissaPrec; // The number of bits of precision that are present in the mantissa.
206 sint04 base10Exp; // The (derived) base 10 exponent of value.
207 bool finished; // Set to true when we've output all relevant digits.
208
209 private:
210 bool lowOk; // For IEEE unbiased rounding, this is true when mantissa is even. When true, use >= in mMinus test instead of >
211 bool highOk; // Ditto, but for mPlus test.
212
213 // If !bFastEstimateOk, use these.
214
215 // If bFastEstimateOk, use these - same as above, but integer value stored in double.
216 fltp08 dr;
217 fltp08 ds;
218 fltp08 dMPlus;
219 fltp08 dMMinus;
220
221 // Estimate base 10 exponent of number, scale r,s,mPlus,mMinus appropriately.
222 // Returns result of fixup_ExponentEstimate(est).
223 sint04 scale()
224 {
225 // estimate base10 exponent:
226 sint04 base2Exponent = e + mantissaPrec - 1;
227 sint04 exponentEstimate = (sint04)ceil((base2Exponent * kLog2_10) - 0.0000000001);
228
229 fltp08 scale = quickPowTen((exponentEstimate > 0) ? exponentEstimate : -exponentEstimate);
230
231 if (exponentEstimate >= 0)
232 {
233 ds *= scale;
234 return fixup_ExponentEstimate(exponentEstimate);
235 }
236 else
237 {
238 dr *= scale;
239 dMPlus *= scale;
240 dMMinus *= scale;
241 return fixup_ExponentEstimate(exponentEstimate);
242 }
243 }
244
245 // Used by scale to adjust for possible off-by-one error in the base 10 exponent estimate.
246 // Returns exact base10 exponent of number.
247 sint04 fixup_ExponentEstimate(sint04 exponentEstimate)
248 {
249 sint04 correctedEstimate;
250 uint01 quotient = cast<uint01>((dr * 10) / ds);
251 if ((highOk ? (dr + dMPlus) >= ds : dr + dMPlus > ds) || quotient >= 10)
252 {
253 correctedEstimate = exponentEstimate + 1;
254 }
255 else
256 {
257 dr *= 10;
258 dMPlus *= 10;
259 dMMinus *= 10;
260 correctedEstimate = exponentEstimate;
261 }
262 quotient = cast<uint01>(dr / ds);
263 lib_assert(quotient < 10, "quotient cannot be > 9");
264 return correctedEstimate;
265 }
266 };
267 public:
268 enum flt_to_string {
269 DTOSTR_NORMAL,
270 DTOSTR_FIXED,
271 DTOSTR_PRECISION,
272 DTOSTR_EXPONENTIAL
273 };
274
275 template<class t_type>
276 constexpr static void writeInt(String& string, t_type initial_value)
277 {
278 t_type value = initial_value;
279 constexpr uint04 max_digits = MaxDigits<t_type>() + (ObjectInfo<t_type>::Unsigned ? 0 : 1U);
280 char digits[max_digits];
281 uint04 index = max_digits;
282 if constexpr (!ObjectInfo<t_type>::Unsigned)
283 {
284 if (initial_value < cast<t_type>(0))
285 value = cast<t_type>(0) - value;
286 }
287 if (value == 0)
288 {
289 string.add('0');
290 return;
291 }
292 while (index != 0)
293 {
294 t_type j = value;
295 value = value / cast<t_type>(10);
296 j -= (value * cast<t_type>(10));
297 digits[--index] = (cast<char>(j) + '0');
298 if (value == cast<t_type>(0))
299 break;
300 }
301 if constexpr ((!ObjectInfo<t_type>::Unsigned))
302 {
303 if(initial_value < cast<t_type>(0))
304 digits[--index] = '-';
305 }
306 string.addAll(&digits[index], max_digits - index);
307 }
308 template<class t_type>
309 constexpr static void writeHex(String& string, t_type initial_value)
310 {
311 t_type value = initial_value;
312 constexpr uint04 max_digits = MaxDigits<t_type>() + (ObjectInfo<t_type>::Unsigned ? 0 : 1U);
313 char digits[max_digits];
314 uint04 index = max_digits;
315 if ((!ObjectInfo<t_type>::Unsigned) && initial_value < cast<t_type>(0))
316 {
317 value = cast<t_type>(0) - value;
318 }
319 if (value == 0)
320 {
321 string.add('0');
322 return;
323 }
324 while (index != 0)
325 {
326 t_type j = value;
327 value = value / cast<t_type>(16);
328 j -= (value * cast<t_type>(16));
329
330 if (j < 10)
331 digits[--index] = '0' + cast<char>(j);
332 else
333 {
334 switch (j)
335 {
336 case 10: digits[--index] = 'A'; break;
337 case 11: digits[--index] = 'B'; break;
338 case 12: digits[--index] = 'C'; break;
339 case 13: digits[--index] = 'D'; break;
340 case 14: digits[--index] = 'E'; break;
341 case 15: digits[--index] = 'F'; break;
342 }
343 }
344 if (value == cast<t_type>(0))
345 break;
346 }
347 string.addAll(&digits[index], max_digits - index);
348 }
349
350 static void writeFloat(String& string, fltp08 value, flt_to_string mode = DTOSTR_NORMAL, uint01 precision = 10)
351 {
352 if (value > Constant<fltp08>::Max)
353 {
354 string.addAll("-Infinity");
355 return;
356 }
357 if (value < Constant<fltp08>::Min)
358 {
359 string.addAll("Infinity");
360 return;
361 }
362 if (IsInvalid(value))
363 {
364 string.addAll("NaN");
365 return;
366 }
367 if (mode == DTOSTR_NORMAL)
368 {
369 sint08 intValue = cast<sint08>(value);
370 if ((value == (double)(intValue)) && ((uint32_t)intValue != 0x80000000))
371 {
372 writeInt(string, intValue);
373 return;
374 }
375 }
376 const bool negative = value < 0.0;
377 const bool zero = value == 0.0;
378 if (negative) {
379 value = -value;
380 string.add('-');
381 }
382 D2A d2a = D2A(value, mode != DTOSTR_NORMAL, precision);
383 int32_t exp10 = d2a.expBase10() - 1;
384
385 enum FormatType {
386 kNormal,
387 kExponential,
388 kFraction,
389 kFixedFraction
390 };
391 FormatType format;
392
393 switch (mode) {
394 case DTOSTR_FIXED:
395 {
396 if (exp10 < 0) {
397 format = kFixedFraction;
398 }
399 else {
400 format = kNormal;
401 precision++;
402 }
403 }
404 break;
405 case DTOSTR_PRECISION:
406 {
407 if (exp10 < 0) {
408 format = kFraction;
409 }
410 else if (exp10 >= precision) {
411 format = kExponential;
412 }
413 else {
414 format = kNormal;
415 }
416 }
417 break;
418 case DTOSTR_EXPONENTIAL:
419 format = kExponential;
420 precision++;
421 break;
422 default:
423 if (exp10 < 0 && exp10 > -7) {
424 // Number is of form 0.######
425 if (exp10 < -precision) {
426 exp10 = -precision - 1;
427 }
428 format = kFraction;
429 }
430 else if (std::abs(exp10) > 20) { // ECMA spec 9.8.1
431 format = kExponential;
432 }
433 else {
434 format = kNormal;
435 }
436 }
437
438 // Digit generation.
439 switch (format) {
440 case kNormal:
441 {
442 int32_t digits = 0;
443 int32_t digit;
444 digit = d2a.nextDigit();
445 if (digit > 0)
446 string.add((char)(digit + '0'));
447 while (exp10 > 0)
448 {
449 digit = (d2a.finished) ? 0 : d2a.nextDigit();
450 string.add((char)(digit + '0'));
451 exp10--;
452 digits++;
453 }
454 if (mode == DTOSTR_FIXED)
455 {
456 digits = 0;
457 }
458 if (mode == DTOSTR_NORMAL)
459 {
460 if (!d2a.finished)
461 {
462 string.add('.');
463 while (!d2a.finished)
464 string.add((char)(d2a.nextDigit() + '0'));
465 }
466 }
467 else if (digits < precision - 1)
468 {
469 string.add('.');
470 for (; digits < precision - 1; digits++)
471 {
472 digit = d2a.finished ? 0 : d2a.nextDigit();
473 string.add((char)(digit + '0'));
474 }
475 }
476 }
477 break;
478 case kFixedFraction:
479 {
480 string.add('0'); // Sentinel
481 string.add('.');
482 // Write out leading zeroes
483 int32_t digits = 0;
484 if (exp10 > 0) {
485 while (++exp10 < 10 && digits < precision) {
486 string.add('0');
487 digits++;
488 }
489 }
490 else if (exp10 < 0) {
491 while ((++exp10 < 0) && (precision-- > 0))
492 string.add('0');
493 }
494 if (!IsInvalid(precision))
495 {
496 // Write out significand
497 for (; digits < precision; digits++) {
498 if (d2a.finished)
499 {
500 if (mode == DTOSTR_NORMAL)
501 break;
502 string.add('0');
503 }
504 else {
505 string.add((char)(d2a.nextDigit() + '0'));
506 }
507 }
508 }
509 exp10 = 0;
510 }
511 break;
512 case kFraction:
513 {
514 string.add('0'); // Sentinel
515 string.add('.');
516
517 // Write out leading zeros
518 if (!zero)
519 {
520 for (int32_t i = exp10; i < -1; i++) {
521 string.add('0');
522 }
523 }
524
525 // Write out significand
526 int32_t i = 0;
527 while (!d2a.finished)
528 {
529 string.add((char)(d2a.nextDigit() + '0'));
530 if (mode != DTOSTR_NORMAL && ++i >= precision)
531 break;
532 }
533 if (mode == DTOSTR_PRECISION)
534 {
535 while (i++ < precision)
536 string.add((char)(d2a.finished ? '0' : d2a.nextDigit() + '0'));
537 }
538 exp10 = 0;
539 }
540 break;
541 case kExponential:
542 {
543 int32_t digit;
544 digit = d2a.finished ? 0 : d2a.nextDigit();
545 string.add((char)(digit + '0'));
546 if (((mode == DTOSTR_NORMAL) && !d2a.finished) ||
547 ((mode != DTOSTR_NORMAL) && precision > 1)) {
548 string.add('.');
549 for (int32_t i = 0; i < precision - 1; i++) {
550 if (d2a.finished)
551 {
552 if (mode == DTOSTR_NORMAL)
553 break;
554 string.add('0');
555 }
556 else {
557 string.add((char)(d2a.nextDigit() + '0'));
558 }
559 }
560 }
561 }
562 break;
563 }
564 // Clean up zeroes, and place the exponent
565 if (exp10)
566 {
567 if (!zero)
568 {
569 // Remove trailing zeroes, as might appear in 10e+95
570 while (string.size() > 0 && string.last() == '0')
571 {
572 string.removeLast();
573 exp10++;
574 }
575 }
576
577 // Place the exponent
578 string.add('e');
579 if (exp10 > 0) {
580 string.add('+');
581 }
582 writeInt(string, exp10);
583 }
584 }
585 };
586}
#define lib_assert(expression, message)
Definition LibAssert.h:61
Logic for writing to a number.
Definition NumberWriter.h:50
Definition ACIColor.h:37
uint8_t uint01
-Defines an alias representing a 1 byte, unsigned integer -Can represent exact integer values 0 throu...
Definition BaseValues.hpp:80
uint64_t uint08
-Defines an alias representing an 8 byte, unsigned integer
Definition BaseValues.hpp:106
uint32_t uint04
-Defines an alias representing a 4 byte, unsigned integer -Can represent exact integer values 0 throu...
Definition BaseValues.hpp:96
constexpr t_to cast(const Angle< t_from > &value)
Definition Angle.h:375
double fltp08
Defines an alias representing an 8 byte floating-point number.
Definition BaseValues.hpp:149
Defines for a given type (such as sint04, fltp08, UUID, etc) a maximum, minimum, and reserved 'invali...
Definition BaseValues.hpp:233