Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
FloatConversion.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include <charconv>
5#include <sstream>
6
10#include "Stroika/Foundation/Containers/Common.h"
11#include "Stroika/Foundation/Math/Common.h"
12#include "Stroika/Foundation/Memory/Common.h"
15
17
18 /*
19 ********************************************************************************
20 ******************** FloatConversion::SignificantFigures ***********************
21 ********************************************************************************
22 */
23 constexpr SignificantFigures::SignificantFigures (unsigned int p)
24 : fSignificantFigures_{p}
25 {
26 }
27 constexpr SignificantFigures::SignificantFigures (FullFlag)
28 : fSignificantFigures_{} // kFullPrecision
29 {
30 }
31 template <floating_point T>
32 constexpr auto SignificantFigures::GetEffectiveSignificantFigures () const -> RepType
33 {
34 return fSignificantFigures_.value_or (numeric_limits<T>::max_digits10);
35 }
36 template <IStdBasicStringCompatibleCharacter CHAR>
37 constexpr auto SignificantFigures::Calculate (span<const CHAR> number) -> RepType
38 {
39 // two passes cuz eatLeadingZero depends on scientific, which we find at the end of the text
40 bool dotPresent = false;
41 bool scientific = false;
42 for (const CHAR c : number) {
43 switch (c) {
44 // Some locales use . (most), but europe uses , for same purpose (and used in ISO-8601)
45 case '.':
46 case ',':
47 dotPresent = true;
48 break;
49 case 'e':
50 case 'E':
51 scientific = true;
52 break;
53 }
54 }
55 RepType n{0};
56 RepType trailingZeros{0};
57 bool eatLeadingZeros = not scientific;
58 bool gotoDone = false;
59 for (const CHAR c : number) {
60 switch (c) {
61 case '-':
62 case '+':
63 break;
64 case '0':
65 if (eatLeadingZeros) {
66 // munch
67 }
68 else {
69 ++n;
70 }
71 ++trailingZeros;
72 break;
73 case '1':
74 case '2':
75 case '3':
76 case '4':
77 case '5':
78 case '6':
79 case '7':
80 case '8':
81 case '9':
82 eatLeadingZeros = false;
83 trailingZeros = 0;
84 ++n;
85 break;
86 case '.':
87 case ',':
88 trailingZeros = 0;
89 break;
90 case 'e':
91 case 'E':
92 gotoDone = true; // goto Done; // FIX to use this when c++23
93 break;
94 }
95 if (gotoDone) {
96 break;
97 }
98 }
99 // Done:
100 if (dotPresent) {
101 if (eatLeadingZeros) {
102 // then the trailing zeros we counted were also leading 0, but wern't added to n so do so now
103 n += trailingZeros;
104 }
105 }
106 else {
107 n -= trailingZeros;
108 }
109 return n;
110 }
111
112 /**
113 * From http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf,
114 * init (basic_streambuf...) initializes precision to 6
115 * Stroika need not maintain that default here, but it seems a sensible one...
116 *
117 * \note defined here instead of header cuz needs to be after SignificantFigures::CTOR.
118 */
119 constexpr inline SignificantFigures SignificantFigures::kDefault{6};
120
121 /**
122 * \brief Full precision here means enough digits so that when written out (serialized) - and read back in (deserialized)
123 * you get the exact same answer.
124 *
125 * \see // https://stackoverflow.com/questions/22458355/what-is-the-purpose-of-max-digits10-and-how-is-it-different-from-digits10
126 * numeric_limits<T>::max_digits10
127 *
128 * \note defined here instead of header cuz needs to be after SignificantFigures::CTOR.
129 */
130 constexpr inline SignificantFigures SignificantFigures::kFullPrecision{SignificantFigures::FullFlag::eFull};
131
132 /*
133 ********************************************************************************
134 ********************* FloatConversion::ToStringOptions *************************
135 ********************************************************************************
136 */
137 inline ToStringOptions::ToStringOptions (const locale& l)
138 : fUseLocale_{l}
139 {
140 }
142 : fUseCurrentLocale_{p == PredefinedLocale::eUseCurrentLocale}
143 {
144 }
145 constexpr ToStringOptions::ToStringOptions (ios_base::fmtflags fmtFlags)
146 : fFmtFlags_{fmtFlags}
147 {
148 }
149 constexpr ToStringOptions::ToStringOptions (SignificantFigures precision)
150 : fSignificantFigures_{precision}
151 {
152 }
153 constexpr ToStringOptions::ToStringOptions (FloatFormatType scientificNotation)
154 : fFloatFormat_{scientificNotation}
155 {
156 }
157 DISABLE_COMPILER_GCC_WARNING_START ("GCC diagnostic ignored \"-Wdeprecated-declarations\"");
158 DISABLE_COMPILER_CLANG_WARNING_START ("clang diagnostic ignored \"-Wdeprecated-declarations\"");
160 constexpr ToStringOptions::ToStringOptions (TrimTrailingZerosType trimTrailingZeros)
161 : fTrimTrailingZeros_{trimTrailingZeros == TrimTrailingZerosType::eTrimZeros}
162 {
163 }
164 DISABLE_COMPILER_MSC_WARNING_END (4996)
165 DISABLE_COMPILER_CLANG_WARNING_END ("clang diagnostic ignored \"-Wdeprecated-declarations\"");
166 DISABLE_COMPILER_GCC_WARNING_END ("GCC diagnostic ignored \"-Wdeprecated-declarations\"");
167
168 constexpr ToStringOptions::ToStringOptions (const ToStringOptions& b1, const ToStringOptions& b2)
169 : ToStringOptions{b1}
170 {
171 Memory::CopyToIf (&fSignificantFigures_, b2.fSignificantFigures_);
172 Memory::CopyToIf (&fFmtFlags_, b2.fFmtFlags_);
173 fUseCurrentLocale_ = b2.fUseCurrentLocale_;
174 Memory::CopyToIf (&fUseLocale_, b2.fUseLocale_);
175 Memory::CopyToIf (&fTrimTrailingZeros_, b2.fTrimTrailingZeros_);
176 Memory::CopyToIf (&fFloatFormat_, b2.fFloatFormat_);
177 }
178 template <typename... ARGS>
179 constexpr ToStringOptions::ToStringOptions (const ToStringOptions& b1, const ToStringOptions& b2, ARGS&&... args)
180 : ToStringOptions{ToStringOptions{b1, b2}, forward<ARGS> (args)...}
181 {
182 }
183 constexpr optional<SignificantFigures> ToStringOptions::GetSignificantFigures () const
184 {
185 return fSignificantFigures_;
186 }
187 constexpr optional<bool> ToStringOptions::GetTrimTrailingZeros () const
188 {
189 return fTrimTrailingZeros_;
190 }
191 inline locale ToStringOptions::GetUseLocale () const
192 {
193 if (fUseCurrentLocale_) {
194 return locale{};
195 }
196 if (fUseLocale_) {
197 return *fUseLocale_;
198 }
199 static const locale kCLocale_ = locale::classic ();
200 return kCLocale_;
201 }
203 {
204 if (not fUseLocale_ and not fUseCurrentLocale_) {
205 return true; // very common case
206 }
207 return GetUseLocale () == locale::classic ();
208 }
209 constexpr optional<FloatFormatType> ToStringOptions::GetFloatFormat () const
210 {
211 return fFloatFormat_;
212 }
213 constexpr optional<ios_base::fmtflags> ToStringOptions::GetIOSFmtFlags () const
214 {
215 return fFmtFlags_;
216 }
217
218 namespace Private_ {
219 inline void TrimTrailingZeros_NotSci_ (String* strResult)
220 {
221 // @todo THIS could be more efficient: use StringBuilder and Peek()/Character::AsASCIIQuietly
222 RequireNotNull (strResult);
223 // strip trailing zeros (after decimal point - before they may indicate magnatude)
224 // And don't do if ends with exponential notation e+40 shouldnt get shortned to e+4!
225 Require (not strResult->Find ('e', eCaseInsensitive).has_value ());
226 size_t pastDot = strResult->find ('.');
227 if (pastDot != String::npos) {
228 ++pastDot;
229 size_t len = strResult->length ();
230 size_t pPastLastZero = len;
231 for (; (pPastLastZero - 1) > pastDot; --pPastLastZero) {
232 if ((*strResult)[pPastLastZero - 1] != '0') {
233 break;
234 }
235 }
236 if (pPastLastZero == pastDot + 1 and (*strResult)[pPastLastZero - 1] == '0') {
237 pPastLastZero -= 2; // dont map 3.000 to 3.0, map it to 3
238 }
239 if (len != pPastLastZero) [[unlikely]] { // check common case of no change, but this substring and assign already pretty optimized (not sure helps performance)
240 *strResult = strResult->SubString (0, pPastLastZero);
241 }
242 }
243 }
244 inline void TrimTrailingZeros_MaybeSci_ (String* strResult)
245 {
246 // @todo THIS could be more efficient: use StringBuilder and Peek()/Character::AsASCIIQuietly
247 RequireNotNull (strResult);
248 // strip trailing zeros (after decimal point - before they may indicate magnatude)
249 // And don't do if ends with exponential notation e+40 shouldnt get shortned to e+4!
250 if (optional<size_t> whereIsE = strResult->Find ('e', eCaseInsensitive)) {
251 size_t pastDot = strResult->find ('.');
252 if (pastDot != String::npos) {
253 ++pastDot;
254 size_t len = *whereIsE - 1;
255 size_t pPastLastZero = len;
256 for (; (pPastLastZero - 1) > pastDot; --pPastLastZero) {
257 if ((*strResult)[pPastLastZero - 1] != '0') {
258 break;
259 }
260 }
261 if (len != pPastLastZero) [[unlikely]] { // check common case of no change, but this substring and assign already pretty optimized (not sure helps performance)
262 *strResult = strResult->SubString (0, pPastLastZero) + strResult->SubString (*whereIsE);
263 }
264 }
265 }
266 else {
267 size_t pastDot = strResult->find ('.');
268 if (pastDot != String::npos) {
269 ++pastDot;
270 size_t len = strResult->length ();
271 size_t pPastLastZero = len;
272 for (; (pPastLastZero - 1) > pastDot; --pPastLastZero) {
273 if ((*strResult)[pPastLastZero - 1] != '0') {
274 break;
275 }
276 }
277 if (pPastLastZero == pastDot + 1 and (*strResult)[pPastLastZero - 1] == '0') {
278 pPastLastZero -= 2; // dont map 3.000 to 3.0, map it to 3
279 }
280 if (len != pPastLastZero) [[unlikely]] { // check common case of no change, but this substring and assign already pretty optimized (not sure helps performance)
281 *strResult = strResult->SubString (0, pPastLastZero);
282 }
283 }
284 }
285 }
286 }
287
288 namespace Private_ {
289 // TEMPLATE version of wcstof(etc) - to make easier to call from generic algorithm
290 template <typename T>
291 T CStr2FloatType_ (const wchar_t* s, wchar_t** e);
292 template <typename T>
293 T CStr2FloatType_ (const char* s, char** e);
294 template <>
295 inline float CStr2FloatType_ (const wchar_t* s, wchar_t** e)
296 {
297 return ::wcstof (s, e);
298 }
299 template <>
300 inline double CStr2FloatType_ (const wchar_t* s, wchar_t** e)
301 {
302 return wcstod (s, e);
303 }
304 template <>
305 inline long double CStr2FloatType_ (const wchar_t* s, wchar_t** e)
306 {
307 return wcstold (s, e);
308 }
309 template <>
310 inline float CStr2FloatType_ (const char* s, char** e)
311 {
312 return ::strtof (s, e);
313 }
314 template <>
315 inline double CStr2FloatType_ (const char* s, char** e)
316 {
317 return strtod (s, e);
318 }
319 template <>
320 inline long double CStr2FloatType_ (const char* s, char** e)
321 {
322 return strtold (s, e);
323 }
324 }
325
326 namespace Private_ {
327 template <typename RETURN_TYPE>
328 RETURN_TYPE ToFloat_ViaStrToD_ (const char* start, const char* end, const char** remainder)
329 {
330 Require (start <= end);
331 if (start == end) {
332 if (remainder != nullptr) {
333 *remainder = start;
334 }
335 return Math::nan<RETURN_TYPE> ();
336 }
337 else {
338 char* e = nullptr;
339 const char* cst = start;
340
341 if (remainder == nullptr) {
342 // REJECT strings with leading space in this case - must match exactly
343 if (::isspace (*cst)) {
344 return Math::nan<RETURN_TYPE> ();
345 }
346 }
347
348 Memory::StackBuffer<char> tmp;
349 if (*end != '\0') {
350 // remap addresses - copying to a temporary buffer, so we can nul-terminate string passed to strtod (etc)
351 size_t len = end - start;
352 tmp.GrowToSize (len + 1);
353 (void)::memcpy (tmp.begin (), start, len);
354 cst = tmp.begin ();
355 tmp[len] = '\0';
356 }
357 RETURN_TYPE d = CStr2FloatType_<RETURN_TYPE> (cst, &e);
358 // If asked to return remainder do so.
359 // If NOT asked to return remainder, treat not using the entire string as forcing result to be a Nan (invalid parse of number of not the whole thing is a number)
360 if (remainder == nullptr) {
361 if (e != end) {
362 d = Math::nan<RETURN_TYPE> ();
363 }
364 }
365 else {
366 *remainder = e - cst + start; // adjust in case we remapped data
367 }
368 return d;
369 }
370 }
371 template <typename RETURN_TYPE>
372 RETURN_TYPE ToFloat_ViaStrToD_ (const wchar_t* start, const wchar_t* end, const wchar_t** remainder)
373 {
374 Require (start <= end);
375 if (start == end) {
376 if (remainder != nullptr) {
377 *remainder = start;
378 }
379 return Math::nan<RETURN_TYPE> ();
380 }
381 else {
382 wchar_t* e = nullptr;
383 const wchar_t* cst = start;
384
385 if (remainder == nullptr) {
386 // REJECT strings with leading space in this case - must match exactly
387 if (::iswspace (*cst)) {
388 return Math::nan<RETURN_TYPE> ();
389 }
390 }
391
392 Memory::StackBuffer<wchar_t> tmp;
393 if (*end != '\0') {
394 // remap addresses - copying to a temporary buffer, so we can nul-terminate string passed to strtod (etc)
395 size_t len = end - start;
396 tmp.GrowToSize (len + 1);
397 (void)::memcpy (tmp.begin (), start, len * sizeof (wchar_t));
398 cst = tmp.begin ();
399 tmp[len] = '\0';
400 }
401 RETURN_TYPE d = CStr2FloatType_<RETURN_TYPE> (cst, &e);
402 // If asked to return remainder do so.
403 // If NOT asked to return remainder, treat not using the entire string as forcing result to be a Nan (invalid parse of number of not the whole thing is a number)
404 if (remainder == nullptr) {
405 if (e != end) {
406 d = Math::nan<RETURN_TYPE> ();
407 }
408 }
409 else {
410 *remainder = e - cst + start; // adjust in case we remapped data
411 }
412 return d;
413 }
414 }
415 }
416
417 namespace Private_ {
418#if qStroika_Foundation_Debug_AssertionsChecked || not defined(__cpp_lib_to_chars)
419 inline size_t CalcSignificantFigures_ (const String& numStr, bool countZerosAtEndAfterDecPoint = false)
420 {
421 bool leading = true;
422 bool ignoreRest = false;
423 bool seenDot = false;
424 size_t n{};
425 size_t nTrailingZeros{};
426 numStr.Apply ([&] (Character c) {
427 if (ignoreRest) {
428 return;
429 }
430 if (c == '.') {
431 seenDot = true;
432 }
433 if (c == '+' or c == '-' or c == '.') {
434 return;
435 }
436 if (leading and c == '0') {
437 return;
438 }
439 leading = false;
440 if (c == 'e') {
441 ignoreRest = true;
442 return;
443 }
444 if (countZerosAtEndAfterDecPoint and seenDot) {
445 if (c == '0') {
446 ++nTrailingZeros;
447 }
448 else {
449 nTrailingZeros = 0;
450 }
451 }
452 ++n;
453 });
454 return countZerosAtEndAfterDecPoint ? n : n - nTrailingZeros;
455 }
456#endif
457 template <floating_point FLOAT_TYPE>
458 String ToString_OptimizedForCLocaleAndNoStreamFlags_ (FLOAT_TYPE f, SignificantFigures precision)
459 {
460 using namespace Memory;
461 size_t sz = numeric_limits<FLOAT_TYPE>::max_digits10 + numeric_limits<FLOAT_TYPE>::max_exponent10 + 5; // source? "-1.##e+##\0"
462 StackBuffer<char> buf{Memory::eUninitialized, sz};
463 ptrdiff_t resultStrLen;
464 unsigned int effectivePrecision = precision.GetEffectiveSignificantFigures<FLOAT_TYPE> ();
465
466 // XCode 15 still doesn't define __cpp_lib_to_chars, as well as _LIBCPP_VERSION < 190000, I believe --LGP 2024-07-13
467#if defined(__cpp_lib_to_chars)
468 // empirically, on MSVC, to_chars() is much faster than snprintf (appears 3x apx faster) -- LGP 2021-11-04
469 if (precision == SignificantFigures::kFullPrecision) {
470 resultStrLen = to_chars (buf.begin (), buf.end (), f, chars_format::general).ptr - buf.begin ();
471 }
472 else {
473 resultStrLen = to_chars (buf.begin (), buf.end (), f, chars_format::general, effectivePrecision).ptr - buf.begin ();
474 }
475#else
476 auto mkFmtWithPrecisionArg_ = [] (char* formatBufferStart, [[maybe_unused]] char* formatBufferEnd, char _Spec, bool forceScientific) -> char* {
477 char* fmtPtr = formatBufferStart;
478 *fmtPtr++ = '%';
479 // include precision arg
480 *fmtPtr++ = '.';
481 *fmtPtr++ = '*';
482 if (_Spec != '\0') {
483 *fmtPtr++ = _Spec; // e.g. 'L' qualifier
484 }
485 *fmtPtr++ = forceScientific ? 'e' : 'g'; // format specifier
486 *fmtPtr = '\0';
487 Require (fmtPtr < formatBufferEnd);
488 return formatBufferStart;
489 };
490
491 FLOAT_TYPE useRoundedFloat = Math::Round<FLOAT_TYPE> (f, effectivePrecision);
492 if (precision != SignificantFigures::kFullPrecision) {
493 f = useRoundedFloat;
494 }
495
496 bool forceScientific = fabs (f) >= std::pow (10, effectivePrecision);
497 Stroika_ATTRIBUTE_INDETERMINATE char format[100]; // filled in with mkFmtWithPrecisionArg_
498 resultStrLen = ::snprintf (buf.data (), buf.size (),
499 mkFmtWithPrecisionArg_ (std::begin (format), std::end (format),
500 same_as<FLOAT_TYPE, long double> ? 'L' : '\0', forceScientific),
501 (int)effectivePrecision + 1, f);
502 auto actualPrecIncZeros = CalcSignificantFigures_ (String{span{buf.data (), static_cast<size_t> (resultStrLen)}}, true);
503 if (actualPrecIncZeros > effectivePrecision) {
504 ptrdiff_t nBytes = static_cast<ptrdiff_t> (actualPrecIncZeros) - static_cast<ptrdiff_t> (effectivePrecision);
505 auto numberEnd = buf.data () + resultStrLen;
506 auto ePtr = std::find (buf.data (), numberEnd, 'e');
507 if (ePtr != numberEnd) {
508 Assert (buf.data () <= ePtr - nBytes);
509 memmove (ePtr - nBytes, ePtr, (buf.data () + resultStrLen - ePtr)); // slide 'e+22' back over the lost precision bytes of number
510 }
511 resultStrLen -= nBytes;
512 }
513#endif
514
515 // [[maybe_unused]] auto oo1 = String{span{buf.data (), static_cast<size_t> (resultStrLen)}}.As<wstring> ();
516 // [[maybe_unused]] auto ooo = CalcSignificantFigures_ (String{span{buf.data (), static_cast<size_t> (resultStrLen)}});
517 Verify (resultStrLen > 0 and resultStrLen < static_cast<int> (sz));
518#if qStroika_Foundation_Debug_AssertionsChecked
519 Assert (precision == SignificantFigures::kFullPrecision or
520 CalcSignificantFigures_ (String{span{buf.data (), static_cast<size_t> (resultStrLen)}}) <= effectivePrecision);
521#endif
522 return String{span{buf.data (), static_cast<size_t> (resultStrLen)}};
523 }
524 }
525
526 namespace Private_ {
527 // format "{:.{}f}" - val in fixed notation, with precision digits after the decimal point
528 template <floating_point T>
529 inline string formatFixed_ (T val, unsigned int precision)
530 {
531#if qCompilerAndStdLib_format_dynamic_spec_Buggy
532 return Common::StdCompat::vformat ("{:.{}f}", Common::StdCompat::make_format_args (val, precision));
533#else
534 return Common::StdCompat::format ("{:.{}f}", val, precision);
535#endif
536 }
537 template <floating_point T>
538 inline wstring formatFixed_ (const locale& l, T val, unsigned int precision)
539 {
540#if qCompilerAndStdLib_format_dynamic_spec_Buggy
541 return Common::StdCompat::vformat (l, L"{:.{}f}", Common::StdCompat::make_wformat_args (val, precision));
542#else
543 return Common::StdCompat::format (l, L"{:.{}f}", val, precision);
544#endif
545 }
546 template <floating_point T>
547 inline String formatNonScientific_ (T val, unsigned int nSignificantFigures)
548 {
549 auto compute = [&] () {
550 if (val == 0.0) {
551 // special case for zero cuz cannot compute log10(0)
552 return String{formatFixed_ (0.0, nSignificantFigures)};
553 }
554 else {
555 // Calculate digits before the decimal point
556 int digits_before = static_cast<int> (floor (log10 (abs (val)))) + 1;
557
558 // Precision for 'f' (fixed) is the number of digits AFTER the decimal
559 unsigned int precision = static_cast<unsigned int> (max (0, static_cast<int> (nSignificantFigures) - digits_before));
560
561 return String{formatFixed_ (val, precision)};
562 }
563 };
564 String r = compute ();
565 // @todo RECONSIDER
566 // for example, there is no way for 31200000 to be represented with 6 significant digits (in standard form, you can with scientific)
567 // 212312345.0 with 6 significant digits is 212312000, but we need the extra digits for magnatitude, so not
568 // sure worth losing the action digits we have? CONSIDER!!!!
569 // but for now no ensures --Ensure (SignificantFigures::Calculate (span<const wchar_t>{r.As<wstring> ()}) <= nSignificantFigures);
570 return r;
571 }
572 template <floating_point T>
573 inline String formatNonScientific_ (const locale& l, T val, unsigned int nSignificantFigures)
574 {
575 auto compute = [&] () {
576 if (val == 0.0) {
577 // special case for zero cuz cannot compute log10(0)
578 return formatFixed_ (l, 0.0, nSignificantFigures);
579 }
580 else {
581 // Calculate digits before the decimal point
582 int digits_before = static_cast<int> (floor (log10 (abs (val)))) + 1;
583
584 // Precision for 'f' (fixed) is the number of digits AFTER the decimal
585 unsigned int precision = static_cast<unsigned int> (max (0, static_cast<int> (nSignificantFigures) - digits_before));
586
587 return formatFixed_ (l, val, precision);
588 }
589 };
590 String r = compute ();
591 Ensure (SignificantFigures::Calculate (span<const wchar_t>{r.As<wstring> ()}) == nSignificantFigures);
592 return r;
593 }
594 }
595
596 namespace Private_ {
597 template <floating_point FLOAT_TYPE>
598 String ToString_GeneralCase_ (FLOAT_TYPE f, const ToStringOptions& options)
599 {
600 unsigned int usePrecision =
601 options.GetSignificantFigures ().value_or (SignificantFigures{}).GetEffectiveSignificantFigures<FLOAT_TYPE> ();
602 FloatFormatType usingFormat = options.GetFloatFormat ().value_or (FloatFormatType::eDEFAULT);
603
604 // if no locale, can use this...
605 // (see if I can use formatNonScientific_) logic for other cases...
606 if (usingFormat == FloatFormatType::eStandard) {
607 Assert (options.GetIOSFmtFlags () == nullopt); // doable in principle, looking/mapping each feature, but not trivial, and probably not needed anytime soon
608 // --LGP 2026-02-10
609 if (options.GetUsingLocaleClassic ()) {
610 return formatNonScientific_ (f, usePrecision);
611 }
612 else {
613 return formatNonScientific_ (options.GetUseLocale (), f, usePrecision);
614 }
615 }
616
617 // expensive to construct, and slightly cheaper to just use thread_local version of
618 // the same stringstream each time (only one per thread can be in use)
619 static thread_local stringstream s;
620 static const ios_base::fmtflags kDefaultIOSFmtFlags_ = s.flags (); // Just copy out of the first constructed stringstream
621
622 s.str (string{});
623 s.clear ();
624
625 s.imbue (options.GetUseLocale ());
626
627 // must set explicitly (even if defaulted) because of the thread_local thing
628 s.flags (options.GetIOSFmtFlags ().value_or (kDefaultIOSFmtFlags_));
629
630 {
631 switch (usingFormat) {
634 s.setf (ios_base::scientific, ios_base::floatfield);
635 Assert (usePrecision >= 2);
636 s.precision (usePrecision - 1); // scientific format is n.mmm with m digits of 'precision' - so 1 + m is # of significat digits
637 break;
639 s.unsetf (ios_base::floatfield); // see std::defaultfloat - not same as ios_base::fixed
640 /*
641 * Precision Field: The stream's precision setting (managed by std::setprecision or std::ios_base::precision())
642 * specifies the maximum number of meaningful digits to display in total (both before and after the decimal point),
643 * not a fixed number of digits after the decimal point (so same as SignificantFigures)
644 */
645 s.precision (usePrecision);
646 break;
649 s.setf (ios_base::fixed, ios_base::floatfield);
650 s.precision (usePrecision); // I think wrong but test -
651 break;
653 AssertNotReached (); // handled above
654 break;
655 DISABLE_COMPILER_GCC_WARNING_START ("GCC diagnostic ignored \"-Wdeprecated-declarations\"");
656 DISABLE_COMPILER_CLANG_WARNING_START ("clang diagnostic ignored \"-Wdeprecated-declarations\"");
658 case FloatFormatType::eAutomaticScientific: {
659 s.precision (usePrecision);
660 bool useScientificNotation = abs (f) >= pow (10, usePrecision / 2) or
661 (f != 0 and abs (f) < pow (10, -static_cast<int> (usePrecision) / 2)); // scientific preserves more precision - but non-scientific looks better
662 if (useScientificNotation) {
663 s.setf (ios_base::scientific, ios_base::floatfield);
664 }
665 else {
666 s.unsetf (ios_base::floatfield); // see std::defaultfloat - not same as ios_base::fixed
667 s.precision (usePrecision);
668 }
669 } break;
670 DISABLE_COMPILER_MSC_WARNING_END (4996)
671 DISABLE_COMPILER_CLANG_WARNING_END ("clang diagnostic ignored \"-Wdeprecated-declarations\"");
672 DISABLE_COMPILER_GCC_WARNING_END ("GCC diagnostic ignored \"-Wdeprecated-declarations\"");
673 default:
675 break;
676 }
677 }
678
679 s << f;
680 string ss = s.str ();
681
682 return options.GetUsingLocaleClassic () ? String{ss} : String::FromNarrowString (ss, options.GetUseLocale ());
683 }
684 }
685
686 namespace Private_ {
687 template <floating_point FLOAT_TYPE>
688 String ToString_String_Implementation_ (FLOAT_TYPE f, const ToStringOptions& options)
689 {
690 auto floatFormat = options.GetFloatFormat ().value_or (FloatFormatType::eDEFAULT);
691 auto result =
692 (options.GetUsingLocaleClassic () and not options.GetIOSFmtFlags () and floatFormat == FloatFormatType::eDefaultFloat)
693 ? Private_::ToString_OptimizedForCLocaleAndNoStreamFlags_ (f, options.GetSignificantFigures ().value_or (SignificantFigures{}))
694 : Private_::ToString_GeneralCase_ (f, options);
695 if (floatFormat == FloatFormatType::eFixedPointWithWhitespaceTrimmed or floatFormat == FloatFormatType::eStandard) {
696 TrimTrailingZeros_NotSci_ (&result);
697 }
698 if (floatFormat == FloatFormatType::eScientificWithWhitespaceTrimmed) {
699 TrimTrailingZeros_MaybeSci_ (&result);
700 }
701#if qCompilerAndStdLib_float2string_defaultfmt_scientificNotStripped_Buggy
702 if (floatFormat == FloatFormatType::eDefaultFloat) {
703 TrimTrailingZeros_MaybeSci_ (&result);
704 }
705#endif
706 return result;
707 }
708 }
709
710 namespace Private_ {
711 //
712 // This RESPECTS THE CURRENT LOCALE
713 // this private function allows nullptr remainder, and behaves somewhat differently if null or not (unlike PUBLIC API)
714 // Roughly (ignoring precision and character set)
715 // Calls strtod
716 // Strtod Skips leading whitespace, but this routine does NOT allow that
717 // if remainder==null, rejects if not all characters eaten (else returns how many eaten in remainder)
718 //
719 // Note - inlined though long, because most of the logic gets compiled out depending on (template or actual)
720 // parameters, and want to assure those 'seen' by optimizer so most of the code eliminated.
721 //
722 template <typename T, IUNICODECanUnambiguouslyConvertFrom CHAR_T>
723 inline T ToFloat_RespectingLocale_ (const span<const CHAR_T> srcSpan, typename span<const CHAR_T>::iterator* remainder)
724 {
725 if (srcSpan.empty ()) {
726 if (remainder != nullptr) {
727 *remainder = srcSpan.begin ();
728 }
729 return Math::nan<T> ();
730 }
731
732 // because strtod, etc, require a NUL-terminated string, and span doesn't generally provide one,
733 // we must copy to a temporary buffer (not super costly, especially since this isn't the main
734 // approach tried typically)
735 Memory::StackBuffer<CHAR_T> srcBufWithNul{Memory::eUninitialized, srcSpan.size () + 1};
736 Memory::CopyBytes (srcSpan, span{srcBufWithNul});
737 srcBufWithNul[srcSpan.size ()] = '\0';
738 const CHAR_T* si = srcBufWithNul.begin ();
739 const CHAR_T* ei = srcBufWithNul.end () - 1; // buf nul-terminated, but dont treat the NUL as part of our length
740 const CHAR_T* ri = ei;
741
742 // since strtod skips leading whitespace, prevent that
743 if (remainder == nullptr) {
744 bool isSpace;
745 if constexpr (sizeof (CHAR_T) == 1) {
746 isSpace = std::isspace (*si);
747 }
748 else if constexpr (sizeof (CHAR_T) == sizeof (wchar_t)) {
749 isSpace = std::iswspace (*si);
750 }
751 else {
752 isSpace = std::iswspace (static_cast<wint_t> (*si)); // not sure how to check without complex conversion logic
753 }
754 if (isSpace) {
755 if (remainder != nullptr) {
756 *remainder = srcSpan.begin ();
757 }
758 return Math::nan<T> ();
759 }
760 }
761 Stroika_ATTRIBUTE_INDETERMINATE T d; // - set below
762 static_assert (same_as<T, float> or same_as<T, double> or same_as<T, long double>);
763 if constexpr (sizeof (CHAR_T) == 1) {
764 if constexpr (same_as<T, float>) {
765 d = ::strtof (reinterpret_cast<const char*> (si), const_cast<char**> (reinterpret_cast<const char**> (&ri)));
766 }
767 else if constexpr (same_as<T, double>) {
768 d = ::strtod (reinterpret_cast<const char*> (si), const_cast<char**> (reinterpret_cast<const char**> (&ri)));
769 }
770 else if constexpr (same_as<T, long double>) {
771 d = ::strtold (reinterpret_cast<const char*> (si), const_cast<char**> (reinterpret_cast<const char**> (&ri)));
772 }
773 else {
775 }
776 }
777 else if constexpr (sizeof (CHAR_T) == sizeof (wchar_t)) {
778 if constexpr (same_as<T, float>) {
779 d = ::wcstof (reinterpret_cast<const wchar_t*> (si), const_cast<wchar_t**> (reinterpret_cast<const wchar_t**> (&ri)));
780 }
781 else if constexpr (same_as<T, double>) {
782 d = ::wcstod (reinterpret_cast<const wchar_t*> (si), const_cast<wchar_t**> (reinterpret_cast<const wchar_t**> (&ri)));
783 }
784 else if constexpr (same_as<T, long double>) {
785 d = ::wcstold (reinterpret_cast<const wchar_t*> (si), const_cast<wchar_t**> (reinterpret_cast<const wchar_t**> (&ri)));
786 }
787 else {
789 }
790 }
791 else {
792 // must utf convert to wchar_t which we support
793 Memory::StackBuffer<wchar_t> wideBuf{Memory::eUninitialized, UTFConvert::ComputeTargetBufferSize<wchar_t> (srcSpan)};
794 span<const wchar_t> wideSpan = UTFConvert::kThe.ConvertSpan (srcSpan, span{wideBuf});
795 if (remainder == nullptr) {
796 d = ToFloat_RespectingLocale_<T, wchar_t> (wideSpan, nullptr);
797 }
798 else {
799 // do the conversion using wchar_t, and then map back the resulting remainder offset
800 span<const wchar_t>::iterator wideRemainder;
801 d = ToFloat_RespectingLocale_<T> (wideSpan, &wideRemainder);
802 ri = UTFConvert::kThe.ConvertOffset<CHAR_T> (wideSpan, wideRemainder - wideSpan.begin ()) + si;
803 }
804 }
805 if (remainder == nullptr) {
806 // if no remainder argument, we require all the text is 'eaten'
807 if (ri != ei) {
808 d = Math::nan<T> ();
809 }
810 }
811 else {
812 *remainder = srcSpan.begin () + (ri - si);
813 }
814 return d;
815 }
816
817 }
818
819 /*
820 ********************************************************************************
821 ************************** FloatConversion::ToString ***************************
822 ********************************************************************************
823 */
824 template <Common::IAnyOf<String, string, wstring> STRING_TYPE, floating_point FLOAT_TYPE>
825 inline STRING_TYPE ToString (FLOAT_TYPE f, const ToStringOptions& options)
826 {
827 if constexpr (same_as<STRING_TYPE, String>) {
828 return Private_::ToString_String_Implementation_ (f, options);
829 }
830 else if constexpr (same_as<STRING_TYPE, string>) {
831 // @todo improve performance for this case
832 Require (options.GetUsingLocaleClassic ());
833 return Private_::ToString_String_Implementation_ (f, options).AsASCII ();
834 }
835 else if constexpr (same_as<STRING_TYPE, wstring>) {
836 // @todo improve performance for this case
837 Require (options.GetUsingLocaleClassic ());
838 return Private_::ToString_String_Implementation_ (f, options).template As<wstring> ();
839 }
840 }
841
842 /*
843 ********************************************************************************
844 *************************** FloatConversion::ToFloat ***************************
845 ********************************************************************************
846 */
847 template <floating_point T, IUNICODECanUnambiguouslyConvertFrom CHAR_T>
848 T ToFloat (span<const CHAR_T> s)
849 {
850 if constexpr (same_as<remove_cv_t<CHAR_T>, char>) {
851 Require (Character::IsASCII (s));
852 }
853 /*
854 * Most of the time we can do this very efficiently, because there are just ascii characters.
855 * Else, fallback on algorithm that understands full unicode character set, and locales
856 */
858 if (Character::AsASCIIQuietly (s, &asciiS)) {
860#if defined(__cpp_lib_to_chars) and not qCompilerAndStdLib_from_chars_and_tochars_FP_Precision_Buggy
861#if qCompilerAndStdLib_from_chars_reads_past_end_Buggy
862 asciiS.push_back (0);
863#endif
864 auto b = asciiS.begin ();
865 auto e = asciiS.end ();
866#if qCompilerAndStdLib_from_chars_reads_past_end_Buggy
867 e--;
868#endif
869 if (b != e and *b == '+') [[unlikely]] {
870 ++b; // "the plus sign is not recognized outside of the exponent (only the minus sign is permitted at the beginning)" from https://en.cppreference.com/w/cpp/utility/from_chars
871 }
872 auto [ptr, ec] = from_chars (b, e, result);
873 if (ec == errc::result_out_of_range) [[unlikely]] {
874 result = *b == '-' ? -numeric_limits<T>::infinity () : numeric_limits<T>::infinity ();
875 }
876 else if (ec != std::errc{} or ptr != e) [[unlikely]] {
877 //result = Math::nan<T> (); "if # is 100,1 - in funny locale - may need to use legacy algorithm
878 // @todo ADD OPTION arg to ToFloat so we know if C-Locale so can just return NAN here!...
879 result = Private_::ToFloat_RespectingLocale_<T> (s, nullptr);
880 }
881#else
882 result = Private_::ToFloat_RespectingLocale_<T> (span<const char>{asciiS}, nullptr);
883#endif
884 return result;
885 }
886 return Private_::ToFloat_RespectingLocale_<T> (s, nullptr); // fallback for non-ascii strings to old code
887 }
888 template <floating_point T, IUNICODECanUnambiguouslyConvertFrom CHAR_T>
889 T ToFloat (span<const CHAR_T> s, typename span<const CHAR_T>::iterator* remainder)
890 {
891 RequireNotNull (remainder); // use other overload if 'null'
892 if constexpr (same_as<remove_cv_t<CHAR_T>, char>) {
893 Require (Character::IsASCII (s));
894 }
895 /*
896 * Most of the time we can do this very efficiently, because there are just ascii characters.
897 * Else, fallback on algorithm that understands full unicode character set.
898 */
899#if defined(__cpp_lib_to_chars) and not qCompilerAndStdLib_from_chars_and_tochars_FP_Precision_Buggy
901 if (Character::AsASCIIQuietly (s, &asciiS)) {
903 char* b = asciiS.begin ();
904 char* e = asciiS.end ();
905 if (b != e and *b == '+') [[unlikely]] {
906 ++b; // "the plus sign is not recognized outside of the exponent (only the minus sign is permitted at the beginning)" from https://en.cppreference.com/w/cpp/utility/from_chars
907 }
908 auto [ptr, ec] = from_chars (b, e, result);
909 if (ec == errc::result_out_of_range) [[unlikely]] {
910 result = *b == '-' ? -numeric_limits<T>::infinity () : numeric_limits<T>::infinity ();
911 }
912 else if (ec != std::errc{} or ptr != e) [[unlikely]] {
913 //result = Math::nan<T> (); "if # is 100,1 - in funny locale - may need to use legacy algorithm
914 // @todo ADD OPTION arg to ToFloat so we know if C-Locale so can just return NAN here!...
915 typename span<const CHAR_T>::iterator tmpI;
916 result = Private_::ToFloat_RespectingLocale_<T> (s, &tmpI);
917 ptr = tmpI - s.begin () + b;
918 }
919 *remainder = s.begin () + UTFConvert::kThe.ConvertOffset<CHAR_T> (
920 span{reinterpret_cast<const char8_t*> (b), reinterpret_cast<const char8_t*> (e)}, ptr - b);
921 Assert (*remainder <= s.end ());
922 return result;
923 }
924#endif
925 return Private_::ToFloat_RespectingLocale_<T> (s, remainder); // fallback for non-ascii strings to strtod etc code
926 }
927 template <floating_point T, typename STRINGISH_ARG>
928 inline T ToFloat (STRINGISH_ARG&& s)
929 requires (IConvertibleToString<STRINGISH_ARG> or is_convertible_v<STRINGISH_ARG, std::string>)
930 {
931 using DecayedStringishArg = remove_cvref_t<STRINGISH_ARG>;
932 if constexpr (same_as<DecayedStringishArg, const char*> or same_as<DecayedStringishArg, const char8_t*> or
933 same_as<DecayedStringishArg, const char16_t*> or same_as<DecayedStringishArg, const char32_t*> or
934 same_as<DecayedStringishArg, const wchar_t*>) {
935 return ToFloat<T> (span{s, CString::Length (s)});
936 }
937 else if constexpr (same_as<DecayedStringishArg, String>) {
938 Memory::StackBuffer<wchar_t> ignored; // todo optimize for keep ascii case
939 auto sp = s.template GetData<wchar_t> (&ignored);
940 return ToFloat<T> (sp);
941 }
942 else if constexpr (is_convertible_v<DecayedStringishArg, std::string>) {
943 string ss = s;
944 return ToFloat<T> (span{ss.data (), ss.size ()});
945 }
946 else {
947 return ToFloat<T> (String{forward<STRINGISH_ARG> (s)});
948 }
949 }
950 template <floating_point T>
951 inline T ToFloat (const String& s, String* remainder)
952 {
953 RequireNotNull (remainder);
954 Memory::StackBuffer<wchar_t> ignored;
955 span<const wchar_t> srcSpan = s.GetData (&ignored);
956 span<const wchar_t>::iterator tmpRemainder;
957 auto result = ToFloat<T> (srcSpan, &tmpRemainder);
958 *remainder = String{srcSpan.subspan (tmpRemainder - srcSpan.begin ())};
959 return result;
960 }
961
962 //////////////// DEPRECATED STUFF BELOW
963
964 template <typename T>
965 [[deprecated ("Since Stroika v3.0d1 - use span overload")]] inline T ToFloat (const wchar_t* start, const wchar_t* end, const wchar_t** remainder)
966 {
967 Require (start <= end);
968 RequireNotNull (remainder);
970#if defined(__cpp_lib_to_chars) and not qCompilerAndStdLib_from_chars_and_tochars_FP_Precision_Buggy
971 /*
972 * Most of the time we can do this very efficiently, because there are just ascii characters.
973 * Else, fallback on older algorithm that understands full unicode character set.
974 */
975 Memory::StackBuffer<char> asciiS;
976 if (Character::AsASCIIQuietly (span{start, end}, &asciiS)) {
977 auto b = asciiS.begin ();
978 auto originalB = b; // needed to properly compute remainder
979 auto e = asciiS.end ();
980 if (remainder != nullptr) [[unlikely]] {
981 // in remainder mode we skip leading whitespace
982 while (b != e and iswspace (*b)) [[unlikely]] {
983 ++b;
984 }
985 }
986 if (b != e and *b == '+') [[unlikely]] {
987 ++b; // "the plus sign is not recognized outside of the exponent (only the minus sign is permitted at the beginning)" from https://en.cppreference.com/w/cpp/utility/from_chars
988 }
989
990 auto [ptr, ec] = from_chars (b, e, result);
991 if (ec == errc::result_out_of_range) [[unlikely]] {
992 return *b == '-' ? -numeric_limits<T>::infinity () : numeric_limits<T>::infinity ();
993 }
994 // if error - return nan
995 if (ec != std::errc{}) [[unlikely]] {
996 result = Math::nan<T> ();
997 }
998 // If asked to return remainder do so.
999 // If NOT asked to return remainder, treat not using the entire string as forcing result to be a Nan (invalid parse of number of not the whole thing is a number)
1000 if (remainder == nullptr) [[likely]] {
1001 if (ptr != e) [[unlikely]] {
1002 result = Math::nan<T> ();
1003 }
1004 }
1005 else {
1006 *remainder = ptr - originalB + start; // adjust in case we remapped data
1007 }
1008 }
1009 else {
1010 result = Private_::ToFloat_ViaStrToD_<T> (start, end, remainder);
1011 }
1012#else
1013 result = Private_::ToFloat_ViaStrToD_<T> (start, end, remainder);
1014#endif
1015 return result;
1016 }
1017 template <typename T = double>
1018 [[deprecated ("Since Stroika v3.0d1 - use span overload")]] inline T ToFloat (const char* start, const char* end)
1019 {
1020 return ToFloat<T> (span{start, end});
1021 }
1022 template <typename T = double>
1023 [[deprecated ("Since Stroika v3.0d1 - use span overload")]] inline T ToFloat (const wchar_t* start, const wchar_t* end)
1024 {
1025 return ToFloat<T> (span{start, end});
1026 }
1027
1028}
#define RequireNotReached()
Definition Assertions.h:388
#define RequireNotNull(p)
Definition Assertions.h:350
#define AssertNotReached()
Definition Assertions.h:358
#define Verify(c)
Definition Assertions.h:422
#define Stroika_ATTRIBUTE_INDETERMINATE
Stroika_ATTRIBUTE_INDETERMINATE is used where you would use a C++ attribute for a variable that is in...
Definition StdCompat.h:428
constexpr bool IsASCII() const noexcept
Return true iff the given character (or all in span) is (are) in the ascii range [0....
static bool AsASCIIQuietly(span< const CHAR_T > fromS, RESULT_T *into)
static String FromNarrowString(const char *from, const locale &l)
Definition String.inl:342
static constexpr size_t npos
Definition String.h:1416
static const UTFConvert kThe
Nearly always use this default UTFConvert.
Definition UTFConvert.h:374
nonvirtual size_t ConvertOffset(span< const SRC_T > source, size_t srcIndex) const
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
nonvirtual void push_back(Common::ArgByValueType< T > e)
nonvirtual Iterator< T > begin() const
Support for ranged for, and STL syntax in general.
@ eStandard
Somewhat like defaultfloat, but never uses scientific notation.
STRING_TYPE ToString(FLOAT_TYPE f, const ToStringOptions &options={})
static const SignificantFigures kFullPrecision
Sentinal value that is interpretted differently depending on the type passed to GetEffectiveSignifica...
static constexpr auto Calculate(span< const CHAR > number) -> RepType
nonvirtual locale GetUseLocale() const
return the selected locale object
nonvirtual bool GetUsingLocaleClassic() const
return true if locale used is locale::classic() - the 'C' locale; mostly used as optimization/special...