From 5baf5d3daeece33e0767a1c8fbd1b85daa84f45e Mon Sep 17 00:00:00 2001 From: EnilPajic Date: Fri, 26 Jun 2020 21:25:39 +0200 Subject: [PATCH 01/10] Fixed potential bug with -MINIMUM_VALUE Original code had something like `N < 0 ? -N : N` which could fail if `N` has minimum value of appropriate type. For example, in original code this could fail: ```cpp const long long int LL_MIN = std::numeric_limits::min(); //probably: -9223372036854775808 static const char *number = to_string; ``` Also note that this code relies on "modulo on negative first operands" which was implementation defined in C++03 and before, but with C++11 and later it is defined by standard (see `ISO14882:2011(e)`), and this code anyway targets C++17 so we're safe here. --- to_string.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/to_string.hpp b/to_string.hpp index 116cdd8..6117258 100644 --- a/to_string.hpp +++ b/to_string.hpp @@ -23,7 +23,7 @@ struct to_string_t { // fits to the number perfectly. char buf[([] { unsigned int len = N >= 0 ? 1 : 2; - for (auto n = N < 0 ? -N : N; n; len++, n /= base); + for (auto n = N; n; len++, n /= base); return len; }())]; @@ -33,8 +33,8 @@ struct to_string_t { constexpr to_string_t() { auto ptr = buf + sizeof(buf) / sizeof(buf[0]); *--ptr = '\0'; - for (auto n = N < 0 ? -N : N; n; n /= base) - *--ptr = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[n % base]; + for (auto n = N; n; n /= base) + *--ptr = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[(N < 0 ? -1 : 1) * (n % base)]; if (N < 0) *--ptr = '-'; } From 9aa286ebc05d36f7ce4b4c840ac43904a8cc7ade Mon Sep 17 00:00:00 2001 From: neargye Date: Sat, 27 Jun 2020 21:35:10 +0500 Subject: [PATCH 02/10] container-like --- to_string.hpp | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/to_string.hpp b/to_string.hpp index 1d88b1a..b9e0cce 100644 --- a/to_string.hpp +++ b/to_string.hpp @@ -18,15 +18,16 @@ template, int> = 0, std::enable_if_t<(base > 1 && base < 37), int> = 0> -struct to_string_t { +class to_string_t { // The lambda calculates what the string length of N will be, so that `buf` // fits to the number perfectly. - char_type buf[([] { + char_type buf[([]() constexpr noexcept { unsigned int len = N > 0 ? 1 : 2; for (auto n = N < 0 ? -N : N; n; len++, n /= base); return len; }())] = {}; + public: /** * Constructs the object, filling `buf` with the string representation of N. */ @@ -44,14 +45,24 @@ struct to_string_t { } // Support implicit casting to `char *` or `const char *`. - constexpr operator char_type *() { return buf; } - constexpr operator const char_type *() const { return buf; } - - // Support range-based for loops - constexpr auto begin() { return buf; } - constexpr auto begin() const { return buf; } - constexpr auto end() { return buf + sizeof(buf) / sizeof(buf[0]); } - constexpr auto end() const { return buf + sizeof(buf) / sizeof(buf[0]); } + constexpr operator char_type *() noexcept { return buf; } + constexpr operator const char_type *() const noexcept { return buf; } + + constexpr auto size() const noexcept { return sizeof(buf) / sizeof(buf[0]); } + // Element access + constexpr auto data() noexcept { return buf; } + constexpr auto data() const noexcept { return buf; } + constexpr auto operator[](unsigned int i) noexcept { return buf[i]; } + constexpr auto operator[](unsigned int i) const noexcept { return buf[i]; } + constexpr auto front() noexcept { return buf[0]; } + constexpr auto front() const noexcept { return buf[0]; } + constexpr auto back() noexcept { return buf[size() - 1]; } + constexpr auto back() const noexcept { return buf[size() - 1]; } + // Iterators + constexpr auto begin() noexcept { return buf; } + constexpr auto begin() const noexcept { return buf; } + constexpr auto end() noexcept { return buf + size(); } + constexpr auto end() const noexcept { return buf + size(); } }; /** @@ -59,5 +70,5 @@ struct to_string_t { */ template constexpr to_string_t to_string; - + #endif // TCSULLIVAN_TO_STRING_HPP_ From e395be3782f7adf92dbdd1e7438ea038eebecdf3 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sat, 27 Jun 2020 18:31:53 -0400 Subject: [PATCH 03/10] Use end() in constructor --- to_string.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/to_string.hpp b/to_string.hpp index b9e0cce..11e5906 100644 --- a/to_string.hpp +++ b/to_string.hpp @@ -32,9 +32,9 @@ class to_string_t { * Constructs the object, filling `buf` with the string representation of N. */ constexpr to_string_t() noexcept { + auto ptr = end(); + *--ptr = '\0'; if (N != 0) { - auto ptr = buf + sizeof(buf) / sizeof(buf[0]); - *--ptr = '\0'; for (auto n = N < 0 ? -N : N; n; n /= base) *--ptr = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[n % base]; if (N < 0) From 6ae484c8011dc5f10b552932dd3b977b46b4a1f5 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sat, 27 Jun 2020 18:37:33 -0400 Subject: [PATCH 04/10] add const and references to container operations --- to_string.hpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/to_string.hpp b/to_string.hpp index 11e5906..3f04272 100644 --- a/to_string.hpp +++ b/to_string.hpp @@ -51,18 +51,18 @@ class to_string_t { constexpr auto size() const noexcept { return sizeof(buf) / sizeof(buf[0]); } // Element access constexpr auto data() noexcept { return buf; } - constexpr auto data() const noexcept { return buf; } - constexpr auto operator[](unsigned int i) noexcept { return buf[i]; } - constexpr auto operator[](unsigned int i) const noexcept { return buf[i]; } - constexpr auto front() noexcept { return buf[0]; } - constexpr auto front() const noexcept { return buf[0]; } - constexpr auto back() noexcept { return buf[size() - 1]; } - constexpr auto back() const noexcept { return buf[size() - 1]; } + constexpr const auto data() const noexcept { return buf; } + constexpr auto& operator[](unsigned int i) noexcept { return buf[i]; } + constexpr const auto& operator[](unsigned int i) const noexcept { return buf[i]; } + constexpr auto& front() noexcept { return buf[0]; } + constexpr const auto& front() const noexcept { return buf[0]; } + constexpr auto& back() noexcept { return buf[size() - 1]; } + constexpr const auto& back() const noexcept { return buf[size() - 1]; } // Iterators constexpr auto begin() noexcept { return buf; } - constexpr auto begin() const noexcept { return buf; } + constexpr const auto begin() const noexcept { return buf; } constexpr auto end() noexcept { return buf + size(); } - constexpr auto end() const noexcept { return buf + size(); } + constexpr const auto end() const noexcept { return buf + size(); } }; /** From 6e7939000f22dbc955d5aa7feff4db5541205e98 Mon Sep 17 00:00:00 2001 From: clyne Date: Sat, 27 Jun 2020 19:08:48 -0400 Subject: [PATCH 05/10] Change base from unsigned to signed --- to_string.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/to_string.hpp b/to_string.hpp index bf724cd..e7eef93 100644 --- a/to_string.hpp +++ b/to_string.hpp @@ -15,7 +15,7 @@ * @tparam N Number to convert * @tparam base Desired base, can be from 2 to 36 */ -template, int> = 0, std::enable_if_t<(base > 1 && base < 37), int> = 0> class to_string_t { @@ -35,7 +35,7 @@ class to_string_t { auto ptr = end(); *--ptr = '\0'; if (N != 0) { - for (auto n = N < 0 ? -N : N; n; n /= base) + for (auto n = N; n; n /= base) *--ptr = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[(N < 0 ? -1 : 1) * (n % base)]; if (N < 0) *--ptr = '-'; From 56692725154d1e4ff2ef4786107cfda81c44b192 Mon Sep 17 00:00:00 2001 From: clyne Date: Sat, 27 Jun 2020 19:13:18 -0400 Subject: [PATCH 06/10] Changed other base instance to signed --- to_string.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/to_string.hpp b/to_string.hpp index e7eef93..eb363f0 100644 --- a/to_string.hpp +++ b/to_string.hpp @@ -68,7 +68,7 @@ class to_string_t { /** * Simplifies use of `to_string_t` from `to_string_t()` to `to_string`. */ -template +template constexpr to_string_t to_string; #endif // TCSULLIVAN_TO_STRING_HPP_ From 9503ccb716b563636709b5e90114286d2832f816 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sun, 28 Jun 2020 09:20:12 -0400 Subject: [PATCH 07/10] added f_to_string --- f_to_string.hpp | 90 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 f_to_string.hpp diff --git a/f_to_string.hpp b/f_to_string.hpp new file mode 100644 index 0000000..3d0fd18 --- /dev/null +++ b/f_to_string.hpp @@ -0,0 +1,90 @@ +/** + * f_to_string.hpp - Provides compile-time floating-point-to-string conversion. + * Written by Clyne Sullivan. + * https://github.com/tcsullivan/constexpr-to-string + */ + +#ifndef TCSULLIVAN_F_TO_STRING_HPP_ +#define TCSULLIVAN_F_TO_STRING_HPP_ + +struct f_to_string_double_wrapper { + long long int whole = 0; + long long int frac = 0; + + constexpr f_to_string_double_wrapper(double v, int prec = 5) { + whole = static_cast(v); + v -= whole; + for (int i = 0; i < prec; i++) + v *= 10; + frac = static_cast(v); + } +}; + +/** + * @struct f_to_string_t + * @brief Provides the ability to convert a floating-point number to a string at compile-time. + * @tparam N Number to convert + */ +template +class f_to_string_t { + char_type buf[([]() constexpr noexcept { + unsigned int len = 2; + if (N.whole <= 0) len++; + for (auto n = N.whole; n; len++, n /= 10); + if (N.frac == 0 || (N.whole == 0 && N.frac < 0)) len++; + for (auto n = N.frac; n; len++, n /= 10); + return len; + }())] = {}; + + public: + /** + * Constructs the object, filling `buf` with the string representation of N. + */ + constexpr f_to_string_t() noexcept { + auto append = [](auto V, auto& ptr) { + if (V != 0) { + for (auto n = V; n; n /= 10) + *--ptr = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[(V < 0 ? -1 : 1) * (n % 10)]; + } else { + *--ptr = '0'; + } + }; + + auto ptr = end(); + *--ptr = '\0'; + append(N.frac, ptr); + *--ptr = '.'; + append(N.whole, ptr); + if (N.frac < 0 || N.whole < 0) + *--ptr = '-'; + } + + // Support implicit casting to `char *` or `const char *`. + constexpr operator char_type *() noexcept { return buf; } + constexpr operator const char_type *() const noexcept { return buf; } + + constexpr auto size() const noexcept { return sizeof(buf) / sizeof(buf[0]); } + // Element access + constexpr auto data() noexcept { return buf; } + constexpr const auto data() const noexcept { return buf; } + constexpr auto& operator[](unsigned int i) noexcept { return buf[i]; } + constexpr const auto& operator[](unsigned int i) const noexcept { return buf[i]; } + constexpr auto& front() noexcept { return buf[0]; } + constexpr const auto& front() const noexcept { return buf[0]; } + constexpr auto& back() noexcept { return buf[size() - 1]; } + constexpr const auto& back() const noexcept { return buf[size() - 1]; } + // Iterators + constexpr auto begin() noexcept { return buf; } + constexpr const auto begin() const noexcept { return buf; } + constexpr auto end() noexcept { return buf + size(); } + constexpr const auto end() const noexcept { return buf + size(); } +}; + +/** + * Simplifies use of `f_to_string_t` from `f_to_string_t()` to `f_to_string`. + */ +template +constexpr f_to_string_t f_to_string; + +#endif // TCSULLIVAN_F_TO_STRING_HPP_ + From 837c3cc7f7a174979fbc9e66486fa559e440a007 Mon Sep 17 00:00:00 2001 From: clyne Date: Sun, 28 Jun 2020 09:22:44 -0400 Subject: [PATCH 08/10] removed unnecessary base support --- f_to_string.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/f_to_string.hpp b/f_to_string.hpp index 3d0fd18..8b91a90 100644 --- a/f_to_string.hpp +++ b/f_to_string.hpp @@ -44,7 +44,7 @@ class f_to_string_t { auto append = [](auto V, auto& ptr) { if (V != 0) { for (auto n = V; n; n /= 10) - *--ptr = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[(V < 0 ? -1 : 1) * (n % 10)]; + *--ptr = (V < 0 ? -1 : 1) * (n % 10) + '0'; } else { *--ptr = '0'; } From c606eaa166865e9e452638e8afe8e8f1d99ab7dd Mon Sep 17 00:00:00 2001 From: clyne Date: Sun, 28 Jun 2020 09:27:46 -0400 Subject: [PATCH 09/10] Update readme for f_to_string --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f2c55e1..1dd7663 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,11 @@ * Supports converting to any base between 2 and 36 inclusive * No external dependencies, only includes `type_traits` for template parameter checking * Supports custom character types, e.g. `to_string<123, 10, wchar_t>` +* C++20: Supports floating-point-to-string conversion with `f_to_string` **How to use:** -This single header file provides a `to_string` utility, which may be used as below: +The file `to_string.hpp` provides a `to_string` utility, which may be used as below: ```cpp const char *number = to_string<2147483648999954564, 16>; // produces "1DCD65003B9A1884" @@ -22,6 +23,13 @@ With `to_string`, all that will be found in program disassembly are the resultin Try it [on Compiler Explorer](https://godbolt.org/z/T-MFoh). +`f_to_string.hpp`, requiring C++20, provides an `f_to_string` utility for floating-point conversion: + +``` +puts(f_to_string<3.1415926>); // Defaults to 5-point precision: "3.14159" +puts(f_to_string<{3.1415926, 7}>); // Specify precision: "3.1415926" +``` + # How it works The basic structure of `to_string` is shown below: @@ -29,7 +37,7 @@ The basic structure of `to_string` is shown below: ```cpp template struct to_string_t { - char_type buf[]; // Size selection explained later. + char_type buf[]; // Array size determination explained later. constexpr to_string_t() {} // Converts the integer to a string stored in buf. constexpr operator char_type *() {} // These allow for the object to be implicitly converted constexpr operator const char_type *() {} // to a character pointer. From 703b3fb5b0d4db4b590874a64fd97c828aa85190 Mon Sep 17 00:00:00 2001 From: clyne Date: Sun, 28 Jun 2020 12:33:42 -0400 Subject: [PATCH 10/10] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1dd7663..75ad580 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Try it [on Compiler Explorer](https://godbolt.org/z/T-MFoh). `f_to_string.hpp`, requiring C++20, provides an `f_to_string` utility for floating-point conversion: -``` +```cpp puts(f_to_string<3.1415926>); // Defaults to 5-point precision: "3.14159" puts(f_to_string<{3.1415926, 7}>); // Specify precision: "3.1415926" ```