Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
FloatConversion.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Characters_FloatConversion_h_
5#define _Stroika_Foundation_Characters_FloatConversion_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <ios>
10#include <locale>
11#include <optional>
12
14#include "Stroika/Foundation/Common/Common.h"
15
16/**
17 * TODO:
18 * @todo REDO the FromFloat code with span/modern C++ the way I did the ToFloat code
19 *
20 * @todo ToFloat code needs OPTIONS optional argument, to support locales etc.
21 *
22 * @todo Consider moving notion of Precision into Math module. And if so - and maybe otherwise - make
23 * correct.
24 *
25 * using PrecisionType = uint16_t;
26 *
27 * @todo Consider augmenting the Float2StringOptions::Precision support with Float2StringOptions::MantisaLength
28 * which is the number of decimals after the decimal point.
29 *
30 * @todo Then maybe we can lose ios::format_flags option (maybe keep as ARG, but just grab these fields). Maybe name OK as is, but
31 * just add option for "FIXEDWIDTH", and keep idea of changeing backended arg for ios_flags...
32 */
33
35
36 /**
37 * Control needless trailing zeros. For example, 3.000 instead of 3, or 4.2000 versus 4.2. Sometimes desirable (to show precision).
38 * But often not.
39 */
41 eTrimZeros,
42 eDontTrimZeros,
43
44 Stroika_Define_Enum_Bounds (eTrimZeros, eDontTrimZeros)
45 };
46 using TrimTrailingZerosType::eDontTrimZeros;
47 using TrimTrailingZerosType::eTrimZeros;
48
49 enum class PredefinedLocale {
50 eUseCLocale,
51 /**
52 * \note - this selects the current locale at the time the preference is used, whereas
53 * in Stroika v2.1, it used the current locale at the time the preference object was created.
54 */
56 };
57 using PredefinedLocale::eUseCLocale;
59
60 /**
61 * Precision (here) is defined to be the number of significant digits (including before and after decimal point).
62 *
63 * This is used for specifying how to format floating point numbers.
64 *
65 * The special value Precision::kFull refers to when you wish the full precision that allows the exact value to be read back
66 * after being written:
67 *
68 * https://en.cppreference.com/w/cpp/utility/to_chars
69 * https://stackoverflow.com/questions/22458355/what-is-the-purpose-of-max-digits10-and-how-is-it-different-from-digits10
70 *
71 * 3) ...string representation consists of ... and parsing the representation using the corresponding std::from_chars function recovers value exactly ...
72 *
73 * &&& docs below obsolete - CLEANUP but review first: right answer I think in https://stackoverflow.com/questions/22458355/what-is-the-purpose-of-max-digits10-and-how-is-it-different-from-digits10
74 *
75 * Somehow, using digits10, or digits10-1, doesn't appear to really work. Sometimes on some systems for some values. But doesn't appear clearly
76 * documented to work as above on the to_chars function description --LGP 2024-07-12
77 // From https://en.cppreference.com/w/cpp/types/numeric_limits/digits10
78 // The value of std::numeric_limits<T>::digits10 is the number of base-10 digits that can be represented by the type T without change,
79 // that is, any number with this many significant decimal digits can be converted to a value of type T and back to decimal form,
80 // without change due to rounding or overflow. For base-radix types, it is the value of digits() (digits - 1 for floating-point types)
81 // multiplied by log 10 radix and rounded down.
82 */
83 struct Precision {
84 public:
85 /**
86 * Flag indicating full precision (see Precision class docs for explanation) - max_digits10
87 */
88 enum FullFlag {
89 eFull
90 };
91
92 public:
93 /**
94 * Precision ()/0
95 * From http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf,
96 * init (basic_streambuf...) initializes precision to 6
97 * Stroika need not maintain that default here, but it seems a sensible one...
98 * Precision(FullFlag):
99 * special magic value, so depending on type 'T' in call to GetEffectivePrecision () - gets full precision for that type
100 */
101 constexpr Precision () = default;
102 constexpr Precision (unsigned int p);
103 constexpr Precision (FullFlag);
104
105 public:
106 bool operator== (const Precision&) const = default;
107
108 public:
109 /**
110 * Return the used precision. This will be the value specified in the Precision constructor, or
111 * if 'Full' precision given in the constructor, the desired floating point type will be used to compute
112 * the appropriate precision.
113 *
114 * NOTE - its NOT super clear what this should be. But our definition of it is sufficient precision so that when
115 * you read the value back in, (serialize then deserialize) - you get the original value.
116 *
117 * Docs like https://en.cppreference.com/w/cpp/types/numeric_limits/digits10 - seem to suggest this might be
118 * std::numeric_limits<T>::digits10 or std::numeric_limits<T>::digits10-1 for floating point types.
119 *
120 * Docs like https://en.cppreference.com/w/cpp/io/manip/setprecision - suggest it might be:
121 * std::numeric_limits<T>::digits10 + 1;
122 *
123 * Docs like https://en.cppreference.com/w/cpp/utility/to_chars - (3) - suggest none of the above - you must call
124 * to_chars() without specifying a precision.
125 *
126 * \see https://stackoverflow.com/questions/22458355/what-is-the-purpose-of-max-digits10-and-how-is-it-different-from-digits10
127 */
128 template <floating_point T>
129 constexpr unsigned int GetEffectivePrecision () const;
130
131 public:
132 /**
133 * @see Characters::ToString ();
134 */
135 nonvirtual String ToString () const;
136
137 public:
138 static const Precision kFull;
139
140 private:
141 // if missing, implies == kFull
142 optional<unsigned int> fPrecision_{6};
143 };
144
145 /**
146 * Automatic picks based on the precision and the number used, so for example, 0.0000001
147 * will show as '1e-7', but 4 will show as '4'
148 *
149 * eScientific corresponds to ios_base::scientific
150 * eFixedPoint corresponds to ios_base::fixed
151 * eDefaultFloat corresponds to unsetf (floatfield) - which may be different than scientific or fixed point
152 */
153 enum class FloatFormatType {
154 eScientific,
155 eDefaultFloat,
156 eFixedPoint,
157 eAutomaticScientific,
158
159 eDEFAULT = eDefaultFloat,
160
161 Stroika_Define_Enum_Bounds (eScientific, eAutomaticScientific)
162 };
163 using FloatFormatType::eAutomaticScientific;
164 using FloatFormatType::eDefaultFloat;
165 using FloatFormatType::eFixedPoint;
166 using FloatFormatType::eScientific;
167
168 /**
169 * These are options for the FloatConversion::ToString () function
170 *
171 * FloatConversion::ToString uses the locale specified by ToStringOptions, but defaults to
172 * the "C" locale.
173 *
174 * This prints and trims any trailing zeros (after the decimal point - fTrimTrailingZeros -
175 * by default.
176 *
177 * Float2String () maps NAN values to the string "NAN", and negative infinite values to "-INF", and positive infinite
178 * values to "INF" (ignoring case).
179 * @see http://en.cppreference.com/w/cpp/string/byte/strtof
180 */
182 public:
183 /**
184 * Default is to use use C-locale
185 * \note - if ios_base::fmtflags are specified, these REPLACE - not merged - with
186 * basic ios flags
187 */
188 constexpr ToStringOptions () = default;
189 constexpr ToStringOptions (PredefinedLocale p);
190 ToStringOptions (const locale& l);
191 constexpr ToStringOptions (ios_base::fmtflags fmtFlags);
192 constexpr ToStringOptions (Precision precision);
193 constexpr ToStringOptions (FloatFormatType floatFormat);
194 constexpr ToStringOptions (TrimTrailingZerosType trimTrailingZeros);
195 ToStringOptions (const ToStringOptions& b1, const ToStringOptions& b2);
196 template <typename... ARGS>
197 ToStringOptions (const ToStringOptions& b1, const ToStringOptions& b2, ARGS&&... args);
198
199 public:
200 nonvirtual optional<Precision> GetPrecision () const;
201
202 public:
203 nonvirtual optional<bool> GetTrimTrailingZeros () const;
204
205 public:
206 /**
207 * \brief return the selected locale object
208 *
209 * \note before Stroika v3.0d1, this returned optional, and for the case of locale::classic, it retuned none
210 */
211 nonvirtual locale GetUseLocale () const;
212
213 public:
214 /**
215 * \brief return true if locale used is locale::classic() - the 'C' locale; mostly used as optimization/special case
216 */
217 nonvirtual bool GetUsingLocaleClassic () const;
218
219 public:
220 nonvirtual optional<FloatFormatType> GetFloatFormat () const;
221
222 public:
223 nonvirtual optional<ios_base::fmtflags> GetIOSFmtFlags () const;
224
225 public:
226 static constexpr bool kDefaultTrimTrailingZeros{true};
227
228 public:
229 /**
230 * @see Characters::ToString ();
231 */
232 nonvirtual String ToString () const;
233
234 private:
235 optional<Precision> fPrecision_;
236 optional<ios_base::fmtflags> fFmtFlags_;
237 bool fUseCurrentLocale_{false}; // dynamically calculated current locale
238 optional<locale> fUseLocale_; // if missing, use locale::classic (unless fUseCurrentLocale_)
239 optional<bool> fTrimTrailingZeros_;
240 optional<FloatFormatType> fFloatFormat_;
241 };
242
243 /**
244 * ToString converts a floating point number to a string, controlled by parameterized options.
245 *
246 * @see ToStringOptions
247 *
248 * ToString () maps NAN values to the string "NAN", and negative infinite values to "-INF", and positive infinite
249 * values to "INF" (note NAN/INF are case insensitive).
250 * @see http://en.cppreference.com/w/cpp/string/byte/strtof
251 *
252 * The supported type values for FLOAT_TYPE are:
253 * o float
254 * o double
255 * o long double
256 *
257 * The supported type values for RESULT_TYPE are:
258 * o String
259 * o string
260 * o wstring
261 * o ... but this could sensibly be extended in the future
262 */
263 template <typename STRING_TYPE = String, floating_point FLOAT_TYPE = float>
264 STRING_TYPE ToString (FLOAT_TYPE f, const ToStringOptions& options = {});
265
266 template <>
267 String ToString (float f, const ToStringOptions& options);
268 template <>
269 String ToString (double f, const ToStringOptions& options);
270 template <>
271 String ToString (long double f, const ToStringOptions& options);
272 template <>
273 string ToString (float f, const ToStringOptions& options);
274 template <>
275 string ToString (double f, const ToStringOptions& options);
276 template <>
277 string ToString (long double f, const ToStringOptions& options);
278 template <>
279 wstring ToString (float f, const ToStringOptions& options);
280 template <>
281 wstring ToString (double f, const ToStringOptions& options);
282 template <>
283 wstring ToString (long double f, const ToStringOptions& options);
284
285 /**
286 * ToFloat all overloads:
287 * Convert the given decimal-format floating point string to an float, double, or long double.
288 *
289 * ToFloat will return nan () if no valid parse (for example, -1.#INF000000000000 is,
290 * invalid and returns nan, despite the fact that this is often emitted by the MSFT sprintf() for inf values).
291 *
292 * The overloads taking string or const char* arguments Require() that the input is ASCII ('C' locale required/assumed).
293 * (@todo revisit this point --LGP 2022-12-28)
294 *
295 * If the argument value is too large or too small to fit in 'T' (ERANGE) - then the value will be
296 * pinned to -numeric_limits<T>::infinity () or numeric_limits<T>::infinity ().
297 *
298 * If the input string is INF or INFINITY (with an optional +/- prefix) - the returned
299 * value will be the appropriate version of infinity.
300 *
301 * If the argument is the string "NAN", a quiet NAN will be returned. If the string -INF or -INFINITY,
302 * a negative infinite float will be returned, and if INF or INFINITY is passed, a positive infinite
303 * value will be returned:
304 * @see http://en.cppreference.com/w/cpp/string/byte/strtof
305 *
306 * @todo TBD/TOCHANGE if using strtod or from_chars - about to add OPTIONS PARAM to decide
307 * For now - tries both
308 *
309 * ToFloat (no remainder parameter):
310 * The argument should be pre-trimmed (whitespace). If there is any leading or trailing garbage (even whitespace)
311 * this function will return nan() (**note - unlike overload with 'remainder' arg**).
312 *
313 * ToFloat (with remainder parameter):
314 * Logically a simple wrapper on std::wcstof, std::wcstod, std::wcstold - except using String class, and returns the
315 * unused portion of the string in the REQUIRED remainder OUT parameter.
316 *
317 * This means it ALLOWS leading whitespace (skipped). And it allows junk at the end (remainder parameter filled in with what).
318 *
319 * \note SEE http://stroika-bugs.sophists.com/browse/STK-748
320 * We will PROBABLY change this API to take a ToFloatOptions parameter to handle proper locale/conversions of strings to numbers
321 * but so far I've not been able to get any of that working, so ignore for now...
322 *
323 * \pre start <= end; for overloads with start/end, and must point to valid string in that range
324 * \pre remainder != nullptr
325 *
326 * // @todo redo all these with some concept to make it shorter - like ISCOVNERTIBLE TO STRING
327 *
328 *
329 * \note when called with CHAR_T=char, we REQUIRE the argument string is ALL ASCII
330 */
331 template <floating_point T = double, IUNICODECanUnambiguouslyConvertFrom CHAR_T>
332 T ToFloat (span<const CHAR_T> s);
333 template <floating_point T = double, IUNICODECanUnambiguouslyConvertFrom CHAR_T>
334 T ToFloat (span<const CHAR_T> s, typename span<const CHAR_T>::iterator* remainder);
335 template <floating_point T = double, typename STRINGISH_ARG>
336 T ToFloat (STRINGISH_ARG&& s)
337 requires (IConvertibleToString<STRINGISH_ARG> or is_convertible_v<STRINGISH_ARG, std::string>);
338 template <floating_point T = double>
339 T ToFloat (const String& s, String* remainder);
340
341}
342
345}
346
347/*
348 ********************************************************************************
349 ***************************** Implementation Details ***************************
350 ********************************************************************************
351 */
352#include "FloatConversion.inl"
353
354#endif /*_Stroika_Foundation_Characters_FloatConversion_h_*/
#define Stroika_Define_Enum_Bounds(FIRST_ITEM, LAST_ITEM)
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
STRING_TYPE ToString(FLOAT_TYPE f, const ToStringOptions &options={})
static const Precision kFull
Full precision here means enough digits so that when written out (serialized) - and read back in (des...
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...