From e627bfa3591971cda7d4f8ed8f24d12baae08caf Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Mon, 1 Jul 2024 05:40:38 -0700 Subject: [PATCH] Introduce ctl::to_string() --- Makefile | 2 +- ctl/BUILD.mk | 1 + ctl/dubble.cc | 35 ++++++++++ ctl/dubble.h | 13 ++++ ctl/ostream.cc | 73 ++++++++++++++++++--- ctl/ostream.h | 5 +- ctl/ostringstream.cc | 58 ++++++++++------- ctl/string.h | 43 +++++++++++++ ctl/to_string.cc | 109 +++++++++++++++++++++++++++++++ test/ctl/copy_test.cc | 6 +- test/ctl/to_string_test.cc | 127 +++++++++++++++++++++++++++++++++++++ 11 files changed, 432 insertions(+), 40 deletions(-) create mode 100644 ctl/dubble.cc create mode 100644 ctl/dubble.h create mode 100644 ctl/to_string.cc create mode 100644 test/ctl/to_string_test.cc diff --git a/Makefile b/Makefile index 73e49877ca4..b6d080060ac 100644 --- a/Makefile +++ b/Makefile @@ -284,10 +284,10 @@ include third_party/ncurses/BUILD.mk # │ include third_party/readline/BUILD.mk # │ include third_party/libunwind/BUILD.mk # | include third_party/libcxxabi/BUILD.mk # | +include third_party/double-conversion/BUILD.mk # │ include ctl/BUILD.mk # │ include third_party/libcxx/BUILD.mk # │ include third_party/openmp/BUILD.mk # │ -include third_party/double-conversion/BUILD.mk # │ include third_party/pcre/BUILD.mk # │ include third_party/less/BUILD.mk # │ include net/https/BUILD.mk # │ diff --git a/ctl/BUILD.mk b/ctl/BUILD.mk index 73cf297662a..3db73331937 100644 --- a/ctl/BUILD.mk +++ b/ctl/BUILD.mk @@ -21,6 +21,7 @@ CTL_A_DIRECTDEPS = \ LIBC_NEXGEN32E \ LIBC_STDIO \ LIBC_STR \ + THIRD_PARTY_DOUBLECONVERSION \ THIRD_PARTY_GDTOA \ THIRD_PARTY_LIBCXXABI \ THIRD_PARTY_LIBUNWIND \ diff --git a/ctl/dubble.cc b/ctl/dubble.cc new file mode 100644 index 00000000000..c80ab67184b --- /dev/null +++ b/ctl/dubble.cc @@ -0,0 +1,35 @@ +// -*- mode:c++; indent-tabs-mode:nil; c-basic-offset:4; coding:utf-8 -*- +// vi: set et ft=cpp ts=4 sts=4 sw=4 fenc=utf-8 :vi +// +// Copyright 2024 Justine Alexandra Roberts Tunney +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted, provided that the +// above copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +// PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include "dubble.h" + +namespace ctl { + +const double_conversion::DoubleToStringConverter kDoubleToPrintfG( + double_conversion::DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN | + double_conversion::DoubleToStringConverter::NO_TRAILING_ZERO, + "inf", + "nan", + 'e', + -6, + 10, // let 32-bit ints be represented without exponent + 6, + 0, + 0); + +} // namespace ctl diff --git a/ctl/dubble.h b/ctl/dubble.h new file mode 100644 index 00000000000..88a2f092448 --- /dev/null +++ b/ctl/dubble.h @@ -0,0 +1,13 @@ +// -*-mode:c++;indent-tabs-mode:nil;c-basic-offset:4;tab-width:8;coding:utf-8-*- +// vi: set et ft=cpp ts=4 sts=4 sw=4 fenc=utf-8 :vi +#ifndef COSMOPOLITAN_CTL_DUBBLE_H_ +#define COSMOPOLITAN_CTL_DUBBLE_H_ +#include "third_party/double-conversion/double-to-string.h" + +namespace ctl { + +extern const double_conversion::DoubleToStringConverter kDoubleToPrintfG; + +} // namespace ctl + +#endif // COSMOPOLITAN_CTL_DUBBLE_H_ diff --git a/ctl/ostream.cc b/ctl/ostream.cc index d57e1190630..f2ab0b302f7 100644 --- a/ctl/ostream.cc +++ b/ctl/ostream.cc @@ -17,11 +17,15 @@ // PERFORMANCE OF THIS SOFTWARE. #include "ostream.h" +#include "dubble.h" +#include "libc/fmt/itoa.h" #include "libc/stdio/stdio.h" #include "string_view.h" namespace ctl { +extern const double_conversion::DoubleToStringConverter kDoubleToPrintfG; + ostream cout(stdout); ostream cerr(stderr); @@ -50,7 +54,7 @@ ostream& ostream::operator<<(const char* str) { if (good() && str) - if (fprintf(file_, "%s", str) < 0) + if (fputs(str, file_) < 0) setstate(badbit); return *this; } @@ -67,35 +71,84 @@ ostream::operator<<(char c) ostream& ostream::operator<<(int n) { - if (good()) - if (fprintf(file_, "%d", n) < 0) + if (good()) { + char buf[12]; + FormatInt32(buf, n); + if (fputs(buf, file_) < 0) + setstate(badbit); + } + return *this; +} + +ostream& +ostream::operator<<(unsigned n) +{ + if (good()) { + char buf[12]; + FormatUint32(buf, n); + if (fputs(buf, file_) < 0) setstate(badbit); + } return *this; } ostream& ostream::operator<<(long n) { - if (good()) - if (fprintf(file_, "%ld", n) < 0) + if (good()) { + char buf[21]; + FormatInt64(buf, n); + if (fputs(buf, file_) < 0) setstate(badbit); + } + return *this; +} + +ostream& +ostream::operator<<(unsigned long n) +{ + if (good()) { + char buf[21]; + FormatUint64(buf, n); + if (fputs(buf, file_) < 0) + setstate(badbit); + } + return *this; +} + +ostream& +ostream::operator<<(float f) +{ + if (good()) { + char buf[128]; + double_conversion::StringBuilder b(buf, sizeof(buf)); + kDoubleToPrintfG.ToShortestSingle(f, &b); + b.Finalize(); + if (fputs(buf, file_) < 0) + setstate(badbit); + } return *this; } ostream& ostream::operator<<(double d) { - if (good()) - if (fprintf(file_, "%f", d) < 0) + if (good()) { + char buf[128]; + double_conversion::StringBuilder b(buf, sizeof(buf)); + kDoubleToPrintfG.ToShortest(d, &b); + b.Finalize(); + if (fputs(buf, file_) < 0) setstate(badbit); + } return *this; } ostream& ostream::operator<<(const string_view& s) { - if (good()) - if (fprintf(file_, "%.*s", (int)s.size(), s.data()) < 0) + if (good() && s.size()) + if (!fwrite(s.data(), s.size(), 1, file_)) setstate(badbit); return *this; } @@ -106,7 +159,7 @@ ostream::operator<<(bool b) if (good()) { const char* value = (flags() & boolalpha) ? (b ? "true" : "false") : (b ? "1" : "0"); - if (fprintf(file_, "%s", value) < 0) + if (fputs(value, file_) < 0) setstate(badbit); } return *this; diff --git a/ctl/ostream.h b/ctl/ostream.h index 6529db5b930..c13d5c1332c 100644 --- a/ctl/ostream.h +++ b/ctl/ostream.h @@ -16,12 +16,15 @@ class ostream : public ios virtual ~ostream(); ostream& operator<<(const char*); + ostream& operator<<(bool); ostream& operator<<(char); ostream& operator<<(int); + ostream& operator<<(unsigned); ostream& operator<<(long); + ostream& operator<<(unsigned long); + ostream& operator<<(float); ostream& operator<<(double); ostream& operator<<(const ctl::string_view&); - ostream& operator<<(bool); ostream& operator<<(ostream& (*)(ostream&)); ostream& put(char); diff --git a/ctl/ostringstream.cc b/ctl/ostringstream.cc index eb7fa51ccb2..dcf01ff6f5e 100644 --- a/ctl/ostringstream.cc +++ b/ctl/ostringstream.cc @@ -17,28 +17,30 @@ // PERFORMANCE OF THIS SOFTWARE. #include "ostringstream.h" +#include "dubble.h" #include "libc/fmt/itoa.h" -#include "libc/stdio/stdio.h" namespace ctl { +extern const double_conversion::DoubleToStringConverter kDoubleToPrintfG; + ostringstream::ostringstream() : buffer_(), write_pos_(0) { } -ostringstream::ostringstream(const ctl::string_view& str) +ostringstream::ostringstream(const string_view& str) : buffer_(str), write_pos_(0) { } -ctl::string +string ostringstream::str() const { return buffer_; } void -ostringstream::str(const ctl::string& s) +ostringstream::str(const string& s) { buffer_ = s; write_pos_ = 0; @@ -65,7 +67,7 @@ ostringstream::operator<<(char c) } ostringstream& -ostringstream::operator<<(const ctl::string_view& s) +ostringstream::operator<<(const string_view& s) { if (good()) { if (write_pos_ + s.size() <= buffer_.size()) { @@ -82,36 +84,40 @@ ostringstream::operator<<(const ctl::string_view& s) ostringstream& ostringstream::operator<<(int n) { - char temp[12]; - if (good()) - *this << ctl::string_view(temp, FormatInt32(temp, n) - temp); + if (good()) { + char buf[12]; + *this << string_view(buf, FormatInt32(buf, n) - buf); + } return *this; } ostringstream& -ostringstream::operator<<(unsigned int n) +ostringstream::operator<<(unsigned n) { - char temp[12]; - if (good()) - *this << ctl::string_view(temp, FormatUint32(temp, n) - temp); + if (good()) { + char buf[12]; + *this << string_view(buf, FormatUint32(buf, n) - buf); + } return *this; } ostringstream& ostringstream::operator<<(long n) { - char temp[21]; - if (good()) - *this << ctl::string_view(temp, FormatInt64(temp, n) - temp); + if (good()) { + char buf[21]; + *this << string_view(buf, FormatInt64(buf, n) - buf); + } return *this; } ostringstream& ostringstream::operator<<(unsigned long n) { - char temp[21]; - if (good()) - *this << ctl::string_view(temp, FormatUint64(temp, n) - temp); + if (good()) { + char buf[21]; + *this << string_view(buf, FormatUint64(buf, n) - buf); + } return *this; } @@ -119,9 +125,11 @@ ostringstream& ostringstream::operator<<(float f) { if (good()) { - char temp[32]; - int len = snprintf(temp, sizeof(temp), "%g", f); - *this << ctl::string_view(temp, len); + char buf[128]; + double_conversion::StringBuilder b(buf, sizeof(buf)); + kDoubleToPrintfG.ToShortestSingle(f, &b); + b.Finalize(); + *this << string_view(buf); } return *this; } @@ -130,9 +138,11 @@ ostringstream& ostringstream::operator<<(double d) { if (good()) { - char temp[32]; - int len = snprintf(temp, sizeof(temp), "%g", d); - *this << ctl::string_view(temp, len); + char buf[128]; + double_conversion::StringBuilder b(buf, sizeof(buf)); + kDoubleToPrintfG.ToShortest(d, &b); + b.Finalize(); + *this << string_view(buf); } return *this; } diff --git a/ctl/string.h b/ctl/string.h index 0509f3839c8..a3488cd7a25 100644 --- a/ctl/string.h +++ b/ctl/string.h @@ -338,11 +338,27 @@ class string return *this; } + string operator+(const char c) const noexcept + { + char s[2] = { c }; + return strcat(*this, s); + } + + string operator+(const string& s) const noexcept + { + return strcat(*this, s); + } + string operator+(const ctl::string_view s) const noexcept { return strcat(*this, s); } + string operator+(const char* s) const noexcept + { + return strcat(*this, s); + } + int compare(const ctl::string_view s) const noexcept { return strcmp(*this, s); @@ -409,6 +425,33 @@ static_assert(sizeof(string) == __::string_size); static_assert(sizeof(__::small_string) == __::string_size); static_assert(sizeof(__::big_string) == __::string_size); +ctl::string +to_string(int); + +ctl::string +to_string(long); + +ctl::string +to_string(long long); + +ctl::string +to_string(unsigned); + +ctl::string +to_string(unsigned long); + +ctl::string +to_string(unsigned long long); + +ctl::string +to_string(float); + +ctl::string +to_string(double); + +ctl::string +to_string(long double); + } // namespace ctl #pragma GCC diagnostic push diff --git a/ctl/to_string.cc b/ctl/to_string.cc new file mode 100644 index 00000000000..fb54968e918 --- /dev/null +++ b/ctl/to_string.cc @@ -0,0 +1,109 @@ +// -*- mode:c++; indent-tabs-mode:nil; c-basic-offset:4; coding:utf-8 -*- +// vi: set et ft=cpp ts=4 sts=4 sw=4 fenc=utf-8 :vi +// +// Copyright 2024 Justine Alexandra Roberts Tunney +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted, provided that the +// above copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +// PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include "dubble.h" +#include "libc/fmt/itoa.h" +#include "libc/math.h" +#include "string.h" +#include "third_party/gdtoa/gdtoa.h" + +namespace ctl { + +extern const double_conversion::DoubleToStringConverter kDoubleToPrintfG; + +string +to_string(int value) +{ + char buf[12]; + return { buf, FormatInt32(buf, value) - buf }; +} + +string +to_string(unsigned value) +{ + char buf[12]; + return { buf, FormatUint32(buf, value) - buf }; +} + +string +to_string(long value) +{ + char buf[21]; + return { buf, FormatInt64(buf, value) - buf }; +} + +string +to_string(unsigned long value) +{ + char buf[21]; + return { buf, FormatUint64(buf, value) - buf }; +} + +string +to_string(long long value) +{ + char buf[21]; + return { buf, FormatInt64(buf, value) - buf }; +} + +string +to_string(unsigned long long value) +{ + char buf[21]; + return { buf, FormatUint64(buf, value) - buf }; +} + +string +to_string(float value) +{ + char buf[128]; + double_conversion::StringBuilder b(buf, sizeof(buf)); + kDoubleToPrintfG.ToShortestSingle(value, &b); + b.Finalize(); + return string(buf); +} + +string +to_string(double value) +{ + char buf[128]; + double_conversion::StringBuilder b(buf, sizeof(buf)); + kDoubleToPrintfG.ToShortest(value, &b); + b.Finalize(); + return string(buf); +} + +string +to_string(long double value) +{ +#if LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024 + return to_string((double)value); +#else + char buf[128]; +#if LDBL_MANT_DIG == 113 + g_Qfmt_p(buf, &value, 16, 128, NIK(2, 0, 0)); +#elif LDBL_MANT_DIG == 64 + g_xfmt_p(buf, &value, 16, 128, NIK(2, 0, 0)); +#else +#error "unsupported long double" +#endif + return string(buf); +#endif +} + +} // namespace ctl diff --git a/test/ctl/copy_test.cc b/test/ctl/copy_test.cc index 2c417186a90..0c73ed7c87a 100644 --- a/test/ctl/copy_test.cc +++ b/test/ctl/copy_test.cc @@ -35,10 +35,9 @@ main() // Test basic copy ctl::copy(src.begin(), src.end(), dest.begin()); - for (size_t i = 0; i < 5; ++i) { + for (size_t i = 0; i < 5; ++i) if (dest[i] != src[i]) return 1; - } // Test partial copy ctl::array dest2 = { 0, 0, 0, 0, 0 }; @@ -57,10 +56,9 @@ main() // Test copy with empty range ctl::array dest4 = { 0, 0, 0, 0, 0 }; ctl::copy(src.begin(), src.begin(), dest4.begin()); - for (size_t i = 0; i < 5; ++i) { + for (size_t i = 0; i < 5; ++i) if (dest4[i] != 0) return 4; - } // Test copy return value ctl::array dest5 = { 0, 0, 0, 0, 0 }; diff --git a/test/ctl/to_string_test.cc b/test/ctl/to_string_test.cc new file mode 100644 index 00000000000..8cba51e6003 --- /dev/null +++ b/test/ctl/to_string_test.cc @@ -0,0 +1,127 @@ +// -*- mode:c++; indent-tabs-mode:nil; c-basic-offset:4; coding:utf-8 -*- +// vi: set et ft=cpp ts=4 sts=4 sw=4 fenc=utf-8 :vi +// +// Copyright 2024 Justine Alexandra Roberts Tunney +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted, provided that the +// above copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +// PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include "ctl/string.h" +#include "libc/intrin/kprintf.h" +#include "libc/limits.h" +#include "libc/math.h" +#include "libc/mem/leaks.h" + +int +main() +{ + + // test integer conversion + { + if (ctl::to_string(0) != "0") + return 1; + if (ctl::to_string(3) != "3") + return 1; + if (ctl::to_string(INT32_MAX) != "2147483647") + return 2; + if (ctl::to_string(INT32_MIN) != "-2147483648") + return 3; + if (ctl::to_string(UINT32_MAX) != "4294967295") + return 4; + if (ctl::to_string(INT64_MAX) != "9223372036854775807") + return 5; + if (ctl::to_string(INT64_MIN) != "-9223372036854775808") + return 6; + if (ctl::to_string(UINT64_MAX) != "18446744073709551615") + return 7; + } + + // test float conversion + // we diverge from std::to_string(float) because it's garbage + { + if (ctl::to_string(0.f) != "0") // 0.000000 + return 8; + if (ctl::to_string(-0.f) != "-0") // 0.000000 + return 9; + if (ctl::to_string(3.f) != "3") // 3.000000 + return 10; + if (ctl::to_string(3.14f) != "3.14") // 3.140000 + return 11; + if (ctl::to_string(3.140001f) != "3.140001") + return 12; + if (ctl::to_string(1000000000.f) != "1000000000") // 1000000000.000000 + return 12; + if (ctl::to_string(10000000000.f) != "1e+10") // 10000000000.000000 + return 12; + if (ctl::to_string(NAN) != "nan") + return 13; + if (ctl::to_string(INFINITY) != "inf") + return 14; + if (ctl::to_string(-INFINITY) != "-inf") + return 15; + if (ctl::to_string(FLT_MIN) != "1.1754944e-38") // 0.000000 lool + return 16; + if (ctl::to_string(-FLT_MIN) != "-1.1754944e-38") + return 17; + if (ctl::to_string(FLT_MAX) != "3.4028235e+38") + return 18; + if (ctl::to_string(-FLT_MAX) != "-3.4028235e+38") + return 19; + } + + // test double conversion + { + if (ctl::to_string(0.) != "0") + return 20; + if (ctl::to_string(-0.) != "-0") + return 21; + if (ctl::to_string(3.) != "3") + return 22; + if (ctl::to_string(2147483647.) != "2147483647") + return 23; + if (ctl::to_string(-2147483648.) != "-2147483648") + return 23; + if (ctl::to_string(10000000000.) != "1e+10") + return 23; + if (ctl::to_string(3.14) != "3.14") + return 23; + if (ctl::to_string(3.140001) != "3.140001") + return 24; + if (ctl::to_string(DBL_MIN) != "2.2250738585072014e-308") + return 25; + if (ctl::to_string(-DBL_MIN) != "-2.2250738585072014e-308") + return 26; + if (ctl::to_string(DBL_MAX) != "1.7976931348623157e+308") + return 27; + if (ctl::to_string(-DBL_MAX) != "-1.7976931348623157e+308") + return 28; + } + + // test long double conversion + { + if (ctl::to_string(0.L) != "0") + return 29; + if (ctl::to_string(-0.L) != "-0") + return 30; + if (ctl::to_string(3.L) != "3") + return 31; + if (ctl::to_string(3.14L) != "3.14") + return 32; + if (ctl::to_string(LDBL_MAX) != "1.189731495357232e+4932") + return 33; + if (ctl::to_string(-LDBL_MAX) != "-1.189731495357232e+4932") + return 34; + } + + CheckForMemoryLeaks(); +}