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