From fe60fc33e054d265272838b99b61d52fbd21e5fe Mon Sep 17 00:00:00 2001 From: Redstone1024 <2824517378@qq.com> Date: Mon, 11 Nov 2024 22:30:15 +0800 Subject: [PATCH] feat(string): add string and arithmetic conversion functions --- .../Source/Private/Testing/StringTesting.cpp | 64 ++++ .../Source/Public/String/Conversion.h.inl | 358 +++++++++++++++++- .../Source/Public/String/String.h | 182 ++++++++- .../Source/Public/String/StringView.h | 59 ++- 4 files changed, 637 insertions(+), 26 deletions(-) diff --git a/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp b/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp index a0c9320..fa45957 100644 --- a/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp +++ b/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp @@ -536,6 +536,70 @@ void TestStringConversion() CheckParseFloat(InPlaceType); CheckParseFloat(InPlaceType); CheckParseFloat(InPlaceType); + + { + always_check(TString::FromBool(true ) == LITERAL(T, "True" )); + always_check(TString::FromBool(false) == LITERAL(T, "False")); + } + + { + always_check(TString::FromInt(42) == LITERAL(T, "42" )); + always_check(TString::FromInt(255, 16) == LITERAL(T, "FF" )); + always_check(TString::FromInt(-42) == LITERAL(T, "-42" )); + always_check(TString::FromInt(0) == LITERAL(T, "0" )); + always_check(TString::FromInt(1234567890) == LITERAL(T, "1234567890")); + always_check(TString::FromInt(255, 2) == LITERAL(T, "11111111" )); + always_check(TString::FromInt(255, 8) == LITERAL(T, "377" )); + always_check(TString::FromInt(255, 36) == LITERAL(T, "73" )); + } + + { + always_check(TString::FromFloat(3.14f) == LITERAL(T, "3.14" )); + always_check(TString::FromFloat(0.0f) == LITERAL(T, "0" )); + always_check(TString::FromFloat(-3.14f) == LITERAL(T, "-3.14" )); + always_check(TString::FromFloat(3.14f, true, false) == LITERAL(T, "3.14" )); + always_check(TString::FromFloat(3.14f, false, true) == LITERAL(T, "3.14e+00")); + always_check(TString::FromFloat(3.14f, false, false, 2) == LITERAL(T, "1.92p+1" )); + always_check(TString::FromFloat(1.0f / 3.0f, true, false, 5) == LITERAL(T, "0.33333" )); + } + + { + always_check( LITERAL_VIEW(T, "True" ).ToBool()); + always_check(!LITERAL_VIEW(T, "False" ).ToBool()); + always_check( LITERAL_VIEW(T, "1" ).ToBool()); + always_check(!LITERAL_VIEW(T, "0" ).ToBool()); + always_check(!LITERAL_VIEW(T, "random").ToBool()); + } + + { + always_check(LITERAL_VIEW(T, "42" ).ToInt() == 42 ); + always_check(LITERAL_VIEW(T, "FF" ).ToInt(16) == 255); + always_check(LITERAL_VIEW(T, "-42" ).ToInt() == -42); + always_check(LITERAL_VIEW(T, "0" ).ToInt() == 0 ); + always_check(LITERAL_VIEW(T, "Invalid").ToInt() == 0 ); + + always_check(LITERAL_VIEW(T, "999999999999999999999999999999").ToInt() == NAMESPACE_STD::numeric_limits::max()); + always_check(LITERAL_VIEW(T, "-999999999999999999999999999999").ToInt() == NAMESPACE_STD::numeric_limits::min()); + } + + { + always_check(LITERAL_VIEW(T, "3.14" ).ToFloat() == 3.14f ); + always_check(LITERAL_VIEW(T, "3.14e+00").ToFloat() == 3.14f ); + always_check(LITERAL_VIEW(T, "-3.14" ).ToFloat() == -3.14f); + always_check(LITERAL_VIEW(T, "0.0" ).ToFloat() == 0.0f ); + + always_check(LITERAL_VIEW(T, "1e+308").ToFloat() == NAMESPACE_STD::numeric_limits::infinity()); + always_check(LITERAL_VIEW(T, "-1e+308").ToFloat() == -NAMESPACE_STD::numeric_limits::infinity()); + always_check(LITERAL_VIEW(T, "1e-308").ToFloat() == 0.0f); + always_check(LITERAL_VIEW(T, "-1e-308").ToFloat() == -0.0f); + + always_check(LITERAL_VIEW(T, "100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.e-4").ToFloat() == NAMESPACE_STD::numeric_limits::infinity()); + always_check(LITERAL_VIEW(T, "-100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.e-4").ToFloat() == -NAMESPACE_STD::numeric_limits::infinity()); + always_check(LITERAL_VIEW(T, "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e-4").ToFloat() == NAMESPACE_STD::numeric_limits::infinity()); + always_check(LITERAL_VIEW(T, "-1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e-4").ToFloat() == -NAMESPACE_STD::numeric_limits::infinity()); + always_check(LITERAL_VIEW(T, "0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e+4").ToFloat() == 0.0f); + always_check(LITERAL_VIEW(T, "-0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e+4").ToFloat() == -0.0f); + } }; Test(InPlaceType); diff --git a/Redcraft.Utility/Source/Public/String/Conversion.h.inl b/Redcraft.Utility/Source/Public/String/Conversion.h.inl index 78e924b..2856ede 100644 --- a/Redcraft.Utility/Source/Public/String/Conversion.h.inl +++ b/Redcraft.Utility/Source/Public/String/Conversion.h.inl @@ -8,9 +8,10 @@ #include #include +#include #pragma warning(push) -#pragma warning(disable : 4146) +#pragma warning(disable : 4146 4244) NAMESPACE_REDCRAFT_BEGIN NAMESPACE_MODULE_BEGIN(Redcraft) @@ -33,7 +34,7 @@ NAMESPACE_PRIVATE_BEGIN template struct TStringHelper { - FORCEINLINE static bool FormatObject(auto& Result, TStringView Fmt, auto& Object) requires (bIsFormat) + static FORCEINLINE bool FormatObject(auto& Result, TStringView Fmt, auto& Object) requires (bIsFormat) { using U = TRemoveCVRef; @@ -119,7 +120,7 @@ struct TStringHelper return false; } - FORCEINLINE static bool ParseObject(TStringView& View, TStringView Fmt, auto& Object) requires (!bIsFormat) + static FORCEINLINE bool ParseObject(TStringView& View, TStringView Fmt, auto& Object) requires (!bIsFormat) { using U = TRemoveCVRef; @@ -328,7 +329,7 @@ struct TStringHelper return false; } - FORCEINLINE static size_t Do(auto& Result, TStringView Fmt, auto ArgsTuple) + static FORCEINLINE size_t Do(auto& Result, TStringView Fmt, auto ArgsTuple) { size_t FormattedObjectNum = 0; @@ -578,6 +579,355 @@ size_t TStringView::Parse(TStringView Fmt, Ts&... Args) const #undef ESCAPE_LEFT_BRACE #undef ESCAPE_RIGHT_BRACE +template +constexpr bool TStringView::ToBool() const +{ + if (StartsWith(LITERAL(T, '1')) + || StartsWith(LITERAL(T, "true")) + || StartsWith(LITERAL(T, "True")) + || StartsWith(LITERAL(T, "TRUE"))) + { + return true; + } + + if (StartsWith(LITERAL(T, '0')) + || StartsWith(LITERAL(T, "false")) + || StartsWith(LITERAL(T, "False")) + || StartsWith(LITERAL(T, "FALSE"))) + { + return false; + } + + return ToInt() != 0; +} + +template +template requires (!CSameAs && !CConst && !CVolatile) +constexpr U TStringView::ToInt(unsigned Base) const +{ + checkf(Base >= 2 && Base <= 36, TEXT("Illegal base. Please check the base.")); + + bool bNegative = false; + + TStringView View = *this; + + if constexpr (CSigned) + { + if (View.StartsWith(LITERAL(ElementType, '-'))) + { + bNegative = true; + View.RemovePrefix(1); + } + } + + using UnsignedU = TMakeUnsigned; + + constexpr UnsignedU UnsignedMaximum = static_cast(-1); + + constexpr U SignedMaximum = static_cast(UnsignedMaximum >> 1); + constexpr U SignedMinimum = -static_cast(SignedMaximum) - 1; + + UnsignedU LastValue = 0; + UnsignedU Value = 0; + + for (ElementType Char : View) + { + auto Digit = TChar::ToDigit(Char, Base); + + if (!Digit) break; + + LastValue = Value; + + Value = static_cast(LastValue * Base + *Digit); + + if (Value < LastValue) + { + if constexpr (CSigned) + { + return bNegative ? SignedMinimum : SignedMaximum; + } + else return UnsignedMaximum; + } + } + + if constexpr (CSigned) + { + if (!bNegative && Value >= static_cast(SignedMaximum)) return SignedMaximum; + if ( bNegative && Value >= static_cast(SignedMinimum)) return SignedMinimum; + + if (bNegative) Value = static_cast(-Value); + } + + return static_cast(Value); +} + +template +template requires (!CConst && !CVolatile) +constexpr U TStringView::ToFloat(bool bFixed, bool bScientific) const +{ + NAMESPACE_STD::chars_format Format; + + if ( bFixed && bScientific) Format = NAMESPACE_STD::chars_format::general; + else if ( bFixed && !bScientific) Format = NAMESPACE_STD::chars_format::fixed; + else if (!bFixed && bScientific) Format = NAMESPACE_STD::chars_format::scientific; + else Format = NAMESPACE_STD::chars_format::hex; + + U Result; + + auto Iter = this->Begin(); + + bool bNegativeMantissa = false; + bool bNegativeExponent = false; + + do + { + if (Iter == this->End()) break; + + if (*Iter == LITERAL(ElementType, '-')) + { + bNegativeMantissa = true; + ++Iter; + } + + auto DecimalPoint = this->End(); + auto NonZeroBegin = this->End(); + + while (Iter != this->End()) + { + if (DecimalPoint == this->End() && *Iter == LITERAL(ElementType, '.')) + { + DecimalPoint = Iter; + } + else if (TChar::IsDigit(*Iter, Format == NAMESPACE_STD::chars_format::hex ? 16 : 10)) + { + if (NonZeroBegin == this->End() && *Iter != LITERAL(ElementType, '0')) + { + NonZeroBegin = Iter; + } + } + else break; + + ++Iter; + } + + if (DecimalPoint == this->End()) DecimalPoint = Iter; + + bNegativeExponent = DecimalPoint < NonZeroBegin; + + if (Iter == this->End()) break; + + bool bHasExponent = false; + + if (Format == NAMESPACE_STD::chars_format::general || Format == NAMESPACE_STD::chars_format::scientific) + { + if (*Iter == LITERAL(ElementType, 'e') || *Iter == LITERAL(ElementType, 'E')) + { + bHasExponent = true; + ++Iter; + } + } + else if (Format == NAMESPACE_STD::chars_format::hex) + { + if (*Iter == LITERAL(ElementType, 'p') || *Iter == LITERAL(ElementType, 'P')) + { + bHasExponent = true; + ++Iter; + } + } + + if (Iter == this->End() || !bHasExponent) break; + + if (*Iter == LITERAL(ElementType, '+')) ++Iter; + if (*Iter == LITERAL(ElementType, '-')) { bNegativeExponent = true; ++Iter; } + + auto ExponentBegin = Iter; + + while (Iter != this->End() && TChar::IsDigit(*Iter, 10)) ++Iter; + + auto ExponentEnd = Iter; + + if (NonZeroBegin == this->End()) break; + + auto Exponent = TStringView(ExponentBegin, ExponentEnd).ToInt(); + + if (bNegativeExponent) Exponent = -Exponent; + + Exponent += static_cast(DecimalPoint - NonZeroBegin); + + bNegativeExponent = Exponent < 0; + } + while (false); + + NAMESPACE_STD::from_chars_result ConvertResult; + + if constexpr (!CSameAs) + { + TArray> Buffer(this->Begin(), Iter); + + ConvertResult = NAMESPACE_STD::from_chars(ToAddress(Buffer.Begin()), ToAddress(Buffer.End()), Result, Format); + } + else ConvertResult = NAMESPACE_STD::from_chars(ToAddress(this->Begin()), ToAddress(this->End()), Result, Format); + + if (ConvertResult.ec == NAMESPACE_STD::errc::result_out_of_range) + { + if (!bNegativeMantissa && !bNegativeExponent) return NAMESPACE_STD::numeric_limits::infinity(); + if ( bNegativeMantissa && !bNegativeExponent) return -NAMESPACE_STD::numeric_limits::infinity(); + if (!bNegativeMantissa && bNegativeExponent) return static_cast( 0.0); + return static_cast(-0.0); + } + + if (ConvertResult.ec == NAMESPACE_STD::errc::invalid_argument) return NAMESPACE_STD::numeric_limits::quiet_NaN(); + + return Result; +} + +template Allocator> +void TString::AppendBool(bool Value) +{ + if (Value) Append(LITERAL(ElementType, "True")); + else Append(LITERAL(ElementType, "False")); +} + +template Allocator> +template requires (!CSameAs && !CConst && !CVolatile) +void TString::AppendInt(U Value, unsigned Base) +{ + checkf(Base >= 2 && Base <= 36, TEXT("Illegal base. Please check the base.")); + + constexpr const ElementType* DigitToChar = LITERAL(ElementType, "0123456789ABCDEF"); + + using UnsignedU = TMakeUnsigned; + + UnsignedU Unsigned = static_cast(Value); + + bool bNegative = false; + + if constexpr (CSigned) + { + if (Value < 0) + { + bNegative = true; + + Unsigned = static_cast(-Unsigned); + } + } + + constexpr size_t BufferSize = sizeof(UnsignedU) * 8 + (CSigned ? 1 : 0); + + ElementType Buffer[BufferSize]; + + ElementType* Iter = Buffer + BufferSize; + + switch (Base) + { + case 0x02: do { *--Iter = static_cast('0' + (Unsigned & 0b00001)); Unsigned >>= 1; } while (Unsigned != 0); break; + case 0x04: do { *--Iter = static_cast('0' + (Unsigned & 0b00011)); Unsigned >>= 2; } while (Unsigned != 0); break; + case 0x08: do { *--Iter = static_cast('0' + (Unsigned & 0b00111)); Unsigned >>= 3; } while (Unsigned != 0); break; + case 0x10: do { *--Iter = DigitToChar[Unsigned & 0b01111]; Unsigned >>= 4; } while (Unsigned != 0); break; + case 0X20: do { *--Iter = DigitToChar[Unsigned & 0b11111]; Unsigned >>= 5; } while (Unsigned != 0); break; + + case 3: + case 5: + case 6: + case 7: + case 9: + case 10: do { *--Iter = static_cast('0' + Unsigned % Base); Unsigned = static_cast(Unsigned / Base); } while (Unsigned != 0); break; + default: do { *--Iter = DigitToChar[Unsigned % Base]; Unsigned = static_cast(Unsigned / Base); } while (Unsigned != 0); break; + } + + if constexpr (CSigned) if (bNegative) *--Iter = LITERAL(T, '-'); + + Append(Iter, Buffer + BufferSize); +} + +NAMESPACE_PRIVATE_BEGIN + +template +struct TStringFloatSerializer +{ + static FORCEINLINE void Do(auto& Result, auto Value, bool bFixed, bool bScientific, unsigned Precision) + { + NAMESPACE_STD::chars_format Format; + + if constexpr (Overload >= 1) + { + if ( bFixed && bScientific) Format = NAMESPACE_STD::chars_format::general; + else if ( bFixed && !bScientific) Format = NAMESPACE_STD::chars_format::fixed; + else if (!bFixed && bScientific) Format = NAMESPACE_STD::chars_format::scientific; + else Format = NAMESPACE_STD::chars_format::hex; + } + + constexpr size_t StartingBufferSize = 64; + + TArray> Buffer(StartingBufferSize / 2); + + NAMESPACE_STD::to_chars_result ConvertResult; + + do + { + Buffer.SetNum(Buffer.Num() * 2); + + if constexpr (Overload == 0) ConvertResult = NAMESPACE_STD::to_chars(ToAddress(Buffer.Begin()), ToAddress(Buffer.End()), Value); + else if constexpr (Overload == 1) ConvertResult = NAMESPACE_STD::to_chars(ToAddress(Buffer.Begin()), ToAddress(Buffer.End()), Value, Format); + else ConvertResult = NAMESPACE_STD::to_chars(ToAddress(Buffer.Begin()), ToAddress(Buffer.End()), Value, Format, Precision); + } + while (ConvertResult.ec == NAMESPACE_STD::errc::value_too_large); + + Buffer.SetNum(ConvertResult.ptr - Buffer.GetData()); + + const bool bNegative = Buffer[0] == '-'; + + const char* Iter = Buffer.GetData() + (bNegative ? 1 : 0); + + if (*Iter == 'i') + { + if (bNegative) Result.Append(LITERAL(T, "-Infinity")); + else Result.Append(LITERAL(T, "Infinity")); + return; + } + + if (*Iter == 'n') + { + if (bNegative) Result.Append(LITERAL(T, "-NaN")); + else Result.Append(LITERAL(T, "NaN")); + return; + } + + unsigned Base; + + if constexpr (Overload == 0) Base = 10; + else Base = Format == NAMESPACE_STD::chars_format::hex ? 16 : 10; + + for (char& Char : Buffer) + { + const auto Digit = FChar::ToDigit(Char, Base); + if (Digit) Char = *FChar::FromDigit(*Digit, Base); + } + + Result.Append(Buffer.Begin(), Buffer.End()); + } +}; + +NAMESPACE_PRIVATE_END + +template Allocator> template requires (!CConst && !CVolatile) +void TString::AppendFloat(U Value) +{ + NAMESPACE_PRIVATE::TStringFloatSerializer::Do(*this, Value, false, false, 0); +} + +template Allocator> template requires (!CConst && !CVolatile) +void TString::AppendFloat(U Value, bool bFixed, bool bScientific) +{ + NAMESPACE_PRIVATE::TStringFloatSerializer::Do(*this, Value, bFixed, bScientific, 0); +} + +template Allocator> template requires (!CConst && !CVolatile) +void TString::AppendFloat(U Value, bool bFixed, bool bScientific, unsigned Precision) +{ + NAMESPACE_PRIVATE::TStringFloatSerializer::Do(*this, Value, bFixed, bScientific, Precision); +} + NAMESPACE_MODULE_END(Utility) NAMESPACE_MODULE_END(Redcraft) NAMESPACE_REDCRAFT_END diff --git a/Redcraft.Utility/Source/Public/String/String.h b/Redcraft.Utility/Source/Public/String/String.h index b6bf97b..a620380 100644 --- a/Redcraft.Utility/Source/Public/String/String.h +++ b/Redcraft.Utility/Source/Public/String/String.h @@ -1022,29 +1022,191 @@ public: public: /** @return true if the string only contains valid characters, false otherwise. */ - NODISCARD FORCEINLINE constexpr bool IsValid() const + NODISCARD FORCEINLINE bool IsValid() const { return TStringView(*this).IsValid(); } /** @return true if the string only contains ASCII characters, false otherwise. */ - NODISCARD FORCEINLINE constexpr bool IsASCII() const + NODISCARD FORCEINLINE bool IsASCII() const { return TStringView(*this).IsASCII(); } /** @return true if the string only contains numeric characters, false otherwise. */ - NODISCARD FORCEINLINE constexpr bool IsNumeric() const - { - return TStringView(*this).IsNumeric(); - } - - /** @return true if the string only contains numeric characters, false otherwise. */ - NODISCARD FORCEINLINE constexpr bool IsNumeric(unsigned Base) const + NODISCARD FORCEINLINE bool IsNumeric(unsigned Base = 10) const { return TStringView(*this).IsNumeric(Base); } +public: + + /** + * Converts a boolean value into a string. + * + * - true becomes "True". + * - false becomes "False". + * + * @return The string containing the boolean value. + */ + NODISCARD static FORCEINLINE TString FromBool(bool Value) + { + TString Result; + + Result.AppendBool(Value); + + return Result; + } + + /** + * Converts an integer value into a string. + * + * @param Base - The base of the number, between [2, 36]. + * + * @return The string containing the integer value. + */ + template requires (!CSameAs && !CConst && !CVolatile) + NODISCARD static FORCEINLINE TString FromInt(U Value, unsigned Base = 10) + { + checkf(Base >= 2 && Base <= 36, TEXT("Illegal base. Please check the base.")); + + TString Result; + + Result.AppendInt(Value, Base); + + return Result; + } + + /** + * Converts a floating-point value into a string. + * The string is formatted using the shortest representation in fixed-point or scientific notation. + * + * @return The string containing the floating-point value. + */ + template requires (!CConst && !CVolatile) + NODISCARD static FORCEINLINE TString FromFloat(U Value) + { + TString Result; + + Result.AppendFloat(Value); + + return Result; + } + + /** + * Converts a floating-point value into a string. + * The string is formatted using the shortest representation in fixed-point or scientific notation. + * The string is formatted using the hex representation if bFixed and bScientific are false. + * + * @param bFixed - The fixed-point format. + * @param bScientific - The scientific notation. + * + * @return The string containing the floating-point value. + */ + template requires (!CConst && !CVolatile) + NODISCARD static FORCEINLINE TString FromFloat(U Value, bool bFixed, bool bScientific) + { + TString Result; + + Result.AppendFloat(Value, bFixed, bScientific); + + return Result; + } + + /** + * Converts a floating-point value into a string. + * The string is formatted using the shortest representation in fixed-point or scientific notation. + * The string is formatted using the hex representation if bFixed and bScientific are false. + * + * @param bFixed - The fixed-point format. + * @param bScientific - The scientific notation. + * @param Precision - The number of digits after the decimal point. + * @return + */ + template requires (!CConst && !CVolatile) + NODISCARD static FORCEINLINE TString FromFloat(U Value, bool bFixed, bool bScientific, unsigned Precision) + { + TString Result; + + Result.AppendFloat(Value, bFixed, bScientific, Precision); + + return Result; + } + + /** Converts a boolean value into a string and appends it to the string. */ + void AppendBool(bool Value); + + /** Converts an integer value into a string and appends it to the string. */ + template requires (!CSameAs && !CConst && !CVolatile) + void AppendInt(U Value, unsigned Base = 10); + + /** Converts a floating-point value into a string and appends it to the string. */ + template requires (!CConst && !CVolatile) + void AppendFloat(U Value); + + /** Converts a floating-point value into a string and appends it to the string. */ + template requires (!CConst && !CVolatile) + void AppendFloat(U Value, bool bFixed, bool bScientific); + + /** Converts a floating-point value into a string and appends it to the string. */ + template requires (!CConst && !CVolatile) + void AppendFloat(U Value, bool bFixed, bool bScientific, unsigned Precision); + + /** + * Converts a string into a boolean value. + * + * - 1, "true", "True", "TRUE" and non-zero integers become true. + * - 0, "false", "False", "FALSE" and unparsable values become false. + * + * @return The boolean value. + */ + NODISCARD FORCEINLINE bool ToBool() const + { + return TStringView(*this).ToBool(); + } + + /** + * Converts a string into an integer value. + * + * - "0x" or "0X" prefixes are not recognized if base is 16. + * - Only the minus sign is recognized (not the plus sign), and only for signed integer types of value. + * - Leading whitespace is not ignored. + * + * Ensure that the entire string can be parsed if IsNumeric(Base, false, true, false) is true. + * + * @param Base - The base of the number, between [2, 36]. + * + * @return The integer value. + */ + template requires (!CSameAs && !CConst && !CVolatile) + NODISCARD FORCEINLINE U ToInt(unsigned Base = 10) const + { + checkf(Base >= 2 && Base <= 36, TEXT("Illegal base. Please check the base.")); + + return TStringView(*this).template ToInt(Base); + } + + /** + * Converts a string into a floating-point value. + * + * - "0x" or "0X" prefixes are not recognized if base is 16. + * - The plus sign is not recognized outside the exponent (only the minus sign is permitted at the beginning). + * - Leading whitespace is not ignored. + * + * Ensure that the entire string can be parsed if bFixed and IsNumeric(10, false) is true. + * Parsers hex floating-point values if bFixed and bScientific are false. + * + * @param bFixed - The fixed-point format. + * @param bScientific - The scientific notation. + * + * @return The floating-point value. + */ + template requires (!CConst && !CVolatile) + NODISCARD FORCEINLINE U ToFloat(bool bFixed = true, bool bScientific = false) const + { + return TStringView(*this).template ToFloat(bFixed, bScientific); + } + public: /** @@ -1067,7 +1229,7 @@ public: * @return The number of objects successfully parsed. */ template - size_t Parse(TStringView Fmt, Ts&... Args) const + FORCEINLINE size_t Parse(TStringView Fmt, Ts&... Args) const { return TStringView(*this).Parse(Fmt, Args...); } diff --git a/Redcraft.Utility/Source/Public/String/StringView.h b/Redcraft.Utility/Source/Public/String/StringView.h index 132f76f..5b12e49 100644 --- a/Redcraft.Utility/Source/Public/String/StringView.h +++ b/Redcraft.Utility/Source/Public/String/StringView.h @@ -402,18 +402,7 @@ public: } /** @return true if the string only contains numeric characters, false otherwise. */ - NODISCARD constexpr bool IsNumeric() const - { - for (ElementType Char : *this) - { - if (!TChar::IsDigit(Char)) return false; - } - - return true; - } - - /** @return true if the string only contains numeric characters, false otherwise. */ - NODISCARD constexpr bool IsNumeric(unsigned Base) const + NODISCARD constexpr bool IsNumeric(unsigned Base = 10) const { for (ElementType Char : *this) { @@ -423,6 +412,52 @@ public: return true; } +public: + + /** + * Converts a string into a boolean value. + * + * - 1, "true", "True", "TRUE" and non-zero integers become true. + * - 0, "false", "False", "FALSE" and unparsable values become false. + * + * @return The boolean value. + */ + NODISCARD constexpr bool ToBool() const; + + /** + * Converts a string into an integer value. + * + * - "0x" or "0X" prefixes are not recognized if base is 16. + * - Only the minus sign is recognized (not the plus sign), and only for signed integer types of value. + * - Leading whitespace is not ignored. + * + * Ensure that the entire string can be parsed if IsNumeric(Base, false, true, false) is true. + * + * @param Base - The base of the number, between [2, 36]. + * + * @return The integer value. + */ + template requires (!CSameAs && !CConst && !CVolatile) + NODISCARD constexpr U ToInt(unsigned Base = 10) const; + + /** + * Converts a string into a floating-point value. + * + * - "0x" or "0X" prefixes are not recognized if base is 16. + * - The plus sign is not recognized outside the exponent (only the minus sign is permitted at the beginning). + * - Leading whitespace is not ignored. + * + * Ensure that the entire string can be parsed if bFixed and IsNumeric(10, false) is true. + * Parsers hex floating-point values if bFixed and bScientific are false. + * + * @param bFixed - The fixed-point format. + * @param bScientific - The scientific notation. + * + * @return The floating-point value. + */ + template requires (!CConst && !CVolatile) + NODISCARD constexpr U ToFloat(bool bFixed = true, bool bScientific = true) const; + public: /**