diff options
Diffstat (limited to 'include/fud_format.hpp')
-rw-r--r-- | include/fud_format.hpp | 390 |
1 files changed, 338 insertions, 52 deletions
diff --git a/include/fud_format.hpp b/include/fud_format.hpp index 1f2e0ae..e156f43 100644 --- a/include/fud_format.hpp +++ b/include/fud_format.hpp @@ -19,6 +19,7 @@ #define FUD_FORMAT_HPP // #include "fud_assert.hpp" +#include "dragonbox/dragonbox.h" #include "fud_array.hpp" #include "fud_option.hpp" #include "fud_result.hpp" @@ -232,7 +233,7 @@ using FormatArgument = std::variant< uint64_t, float, double, - long double, + // long double, // Deliberately not implemented. const utf8*, const char*, StringView, @@ -710,20 +711,12 @@ template <typename T> return prefix; } -FudStatus fillUnsignedBuffer( - Array<utf8, maxIntCharCount>& buffer, - uint64_t value, - uint8_t& bufferLength, - Radix radix, - bool uppercase); +using IntCharArray = Array<utf8, maxIntCharCount>; + +FudStatus fillUnsignedBuffer(IntCharArray& buffer, uint64_t value, uint8_t& bufferLength, Radix radix, bool uppercase); template <typename T> -FudStatus fillSignedBuffer( - Array<utf8, maxIntCharCount>& buffer, - T value, - uint8_t& bufferLength, - Radix radix, - bool uppercase) +FudStatus fillSignedBuffer(IntCharArray& buffer, T value, uint8_t& bufferLength, Radix radix, bool uppercase) { static_assert(sizeof(T) <= sizeof(uint64_t)); static_assert(std::is_signed_v<T>); @@ -762,18 +755,44 @@ FormatResult drainIntegral( template <typename T> FudStatus fillIntegralBuffer( FormatType formatType, - Array<utf8, maxIntCharCount>& buffer, + IntCharArray& buffer, uint8_t& bufferLength, T arg, Radix radix, bool uppercase); +template <typename T> + requires std::is_scalar_v<T> +StringView getSign(FormatSign formatSign, T arg) +{ + StringView sign{""}; + if constexpr (std::is_signed_v<T>) { + if (arg < 0) { + sign = StringView{"-"}; + return sign; + } + } + + switch (formatSign) { + case FormatSign::Plus: + sign = StringView{"+"}; + break; + case FormatSign::Space: + sign = StringView{" "}; + break; + case FormatSign::Minus: + case FormatSign::Default: + default: + break; + } + return sign; +} + template <typename Sink, typename T> requires std::is_integral_v<T> and std::is_scalar_v<T> [[nodiscard]] FormatResult formatIntegral(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, T arg) { static_cast<void>(formatMode); - FormatResult result{0, FudStatus::Success}; if (formatSpec.takesPrecision || formatSpec.precision != FormatSpec::precisionUnspecified) { result.status = FudStatus::FormatInvalid; @@ -800,7 +819,7 @@ template <typename Sink, typename T> return result; } - Array<utf8, maxIntCharCount> buffer{}; + IntCharArray buffer{}; uint8_t bufferLength = 0; result.status = fillIntegralBuffer(formatSpec.formatType, buffer, bufferLength, arg, radix, uppercase); @@ -813,32 +832,14 @@ template <typename Sink, typename T> prefix = StringView{""}; } - StringView sign{""}; - switch (formatSpec.formatSign) { - case FormatSign::Plus: - sign = StringView{"+"}; - break; - case FormatSign::Space: - sign = StringView{" "}; - break; - case FormatSign::Minus: - case FormatSign::Default: - default: - if constexpr (std::is_signed_v<T>) { - if (arg < 0) { - sign = StringView{"-"}; - } - } - break; - } - + StringView sign{getSign(formatSpec.formatSign, arg)}; return drainIntegral(sink, StringView{bufferLength, buffer.data()}, align, fill, width, sign, prefix, padZero); } template <typename T> FudStatus fillIntegralBuffer( FormatType formatType, - Array<utf8, maxIntCharCount>& buffer, + IntCharArray& buffer, uint8_t& bufferLength, T arg, Radix radix, @@ -919,8 +920,8 @@ FormatResult drainIntegral( } if (padZero) { - auto leftPadSize = width - calculatedWidth; - auto padResult = fillPad(sink, '0', leftPadSize); + auto zeroPadSize = width - calculatedWidth; + auto padResult = fillPad(sink, '0', zeroPadSize); result.bytesWritten += padResult.bytesWritten; result.status = padResult.status; } @@ -965,7 +966,7 @@ template <typename Sink> if (result.status != FudStatus::Success) { return result; } - count -= result.bytesWritten; + count -= drainResult.bytesWritten; } } @@ -1009,6 +1010,301 @@ FormatResult rightPad(Sink& sink, FormatAlign::Value align, size_t width, utf8 f return FormatResult{0, FudStatus::Success}; } +[[nodiscard]] Result<bool, FudStatus> validateFloatFormatType(FormatType formatType); + +template <typename Sink> +[[nodiscard]] FormatResult formatNAN(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, bool negative) +{ + auto uppercaseResult = validateFloatFormatType(formatSpec.formatType); + if (uppercaseResult.isError()) { + return FormatResult{0, uppercaseResult.takeError()}; + } + auto uppercase = uppercaseResult.takeOkay(); + + StringView output{"nan"}; + if (negative && uppercase) { + output = StringView{"-NAN"}; + } else if (negative) { + output = StringView{"-nan"}; + } else if (formatSpec.formatSign == FormatSign::Plus && uppercase) { + output = StringView{"+NAN"}; + } else if (formatSpec.formatSign == FormatSign::Plus) { + output = StringView{"+nan"}; + } else if (formatSpec.formatSign == FormatSign::Space && uppercase) { + output = StringView{" NAN"}; + } else if (formatSpec.formatSign == FormatSign::Space) { + output = StringView{" nan"}; + } + + FormatSpec newSpec{}; + newSpec.width = formatSpec.width; + newSpec.fill = formatSpec.fill; + return format(sink, formatMode, newSpec, output); +} + +template <typename Sink> +[[nodiscard]] FormatResult formatINF(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, bool negative) +{ + auto uppercaseResult = validateFloatFormatType(formatSpec.formatType); + if (uppercaseResult.isError()) { + return FormatResult{0, uppercaseResult.takeError()}; + } + auto uppercase = uppercaseResult.takeOkay(); + + StringView output{"inf"}; + if (negative && uppercase) { + output = StringView{"-INF"}; + } else if (negative) { + output = StringView{"-inf"}; + } else if (formatSpec.formatSign == FormatSign::Plus && uppercase) { + output = StringView{"+INF"}; + } else if (formatSpec.formatSign == FormatSign::Plus) { + output = StringView{"+inf"}; + } else if (formatSpec.formatSign == FormatSign::Space && uppercase) { + output = StringView{" INF"}; + } else if (formatSpec.formatSign == FormatSign::Space) { + output = StringView{" inf"}; + } + + FormatSpec newSpec{}; + newSpec.width = formatSpec.width; + newSpec.fill = formatSpec.fill; + return format(sink, formatMode, newSpec, output); +} + +template <typename Sink> +[[nodiscard]] FormatResult formatZero( + Sink& sink, + FormatCharMode formatMode, + const FormatSpec& formatSpec, + bool negative) +{ + FormatResult result{0, FudStatus::NotImplemented}; + static_cast<void>(formatMode); + static_cast<void>(sink); + static_cast<void>(formatSpec); + static_cast<void>(negative); + return result; +} + +constexpr int16_t decimalExpMin = -324; +constexpr int16_t decimalExpMax = 308; +constexpr uint8_t maxDecimalExpLength = 3; +using ExponentBuffer = Array<utf8, 2 + maxDecimalExpLength + 1>; +ExponentBuffer getScientificExponent(int exponent, uint8_t& exponentLength, bool uppercase); + +template <typename Significand> +void fillScientificBuffer(IntCharArray& buffer, Significand significand, uint8_t& bufferLength, auto precision) +{ + constexpr auto radix = static_cast<uint8_t>(Radix::Decimal); + bufferLength = 0; + while (significand > 0) { + auto digit = static_cast<uint8_t>(significand % radix); + buffer[bufferLength++] = digit; + significand /= radix; + } + + constexpr auto roundingPlace = 5; + if (precision + 1 < bufferLength) { + size_t offset = bufferLength - precision - 1; + bool carry = static_cast<uint8_t>(buffer[offset - 1] >= roundingPlace); + + while (carry && offset < bufferLength) { + if (buffer[offset] == radix - 1) { + buffer[offset] = 0; + } else { + buffer[offset]++; + carry = false; + } + offset++; + } + if (carry) { + bufferLength++; + buffer[offset] = 1; + } + } + + forEach(Span<uint8_t>{buffer.data(), bufferLength}, [&](uint8_t digit) { + return static_cast<uint8_t>(digit + '0'); + }); + + for (size_t idx = 0; idx < bufferLength / 2; ++idx) { + auto rhsIndex = bufferLength - idx - 1; + std::swap(buffer[idx], buffer[rhsIndex]); + } +} + +template <typename Sink, typename DecimalRepr> +[[nodiscard]] FormatResult formatScientific( + Sink& sink, + const FormatSpec& formatSpec, + DecimalRepr decimalRepr, + bool uppercase) +{ + FormatResult result{0, FudStatus::Success}; + + if (decimalRepr.significand == 0) { + result.status = FudStatus::ArgumentInvalid; + return result; + } + + constexpr auto defaultPrecision = 6; + if (decimalRepr.exponent < decimalExpMin || decimalRepr.exponent > decimalExpMax) { + result.status = FudStatus::ArgumentInvalid; + return result; + } + + const auto precisionSpecified = formatSpec.precision != FormatSpec::precisionUnspecified; + const auto precision = precisionSpecified ? formatSpec.precision : defaultPrecision; + const bool hasDecimal = formatSpec.alternateForm || precision > 0; + + IntCharArray buffer{}; + uint8_t bufferLength = 0; + fillScientificBuffer(buffer, decimalRepr.significand, bufferLength, precision); + + Array<utf8, maxIntCharCount + 1> floatBuffer{}; + floatBuffer[0] = buffer[0]; + + size_t precisionPlaces = 0; + if (hasDecimal) { + floatBuffer[1] = '.'; + for (size_t idx = 1; idx < bufferLength && precisionPlaces < precision; ++idx) { + floatBuffer[idx + 1] = buffer[idx]; + precisionPlaces++; + } + } + StringView numberView{1 + static_cast<uint8_t>(hasDecimal) + precisionPlaces, floatBuffer.data()}; + + uint8_t exponentLength = 0; + auto exponent = decimalRepr.exponent + bufferLength - 1; + auto exponentBuffer{getScientificExponent(exponent, exponentLength, uppercase)}; + StringView exponentView{exponentLength, exponentBuffer.data()}; + + StringView sign{getSign(formatSpec.formatSign, -1 * static_cast<int>(decimalRepr.is_negative))}; + const size_t calculatedWidth = 1 + precision + sign.length() + static_cast<uint8_t>(hasDecimal) + exponentLength; + + auto fill = formatSpec.fill.fill; + auto align = formatSpec.fill.align.value; + bool padZero = false; + if (align == FormatAlign::Value::Default) { + align = FormatAlign::Value::Right; + fill = formatSpec.leadingZero ? '0' : fill; + padZero = formatSpec.leadingZero; + } + auto width = formatSpec.width; + if (width == FormatSpec::widthUnspecified) { + width = 0; + } + + if (width > calculatedWidth && not padZero) { + auto padResult = leftPad(sink, align, width, fill, calculatedWidth); + result.bytesWritten += padResult.bytesWritten; + result.status = padResult.status; + } + + if (result.status == FudStatus::Success && sign.length() > 0) { + auto drainResult = sink.drain(sign); + result.bytesWritten += drainResult.bytesWritten; + result.status = drainResult.status; + } + + if (result.status == FudStatus::Success && padZero && width > calculatedWidth) { + auto zeroPadSize = width - calculatedWidth; + auto padResult = fillPad(sink, '0', zeroPadSize); + result.bytesWritten += padResult.bytesWritten; + result.status = padResult.status; + } + + if (result.status == FudStatus::Success) { + auto drainNumberResult = sink.drain(numberView); + result.bytesWritten += drainNumberResult.bytesWritten; + result.status = drainNumberResult.status; + } + + if (result.status == FudStatus::Success && precision > precisionPlaces) { + auto remainingPlaces = precision - precisionPlaces; + auto precisionResult = fillPad(sink, '0', remainingPlaces); + result.bytesWritten += precisionResult.bytesWritten; + result.status = precisionResult.status; + } + + if (result.status == FudStatus::Success) { + auto exponentDrainResult = sink.drain(exponentView); + result.bytesWritten += exponentDrainResult.bytesWritten; + result.status = exponentDrainResult.status; + } + + if (result.status == FudStatus::Success) { + auto padResult = rightPad(sink, align, width, fill, calculatedWidth); + result.bytesWritten += padResult.bytesWritten; + result.status = padResult.status; + } + + return result; +} + +template <typename Sink, typename T> + requires std::is_floating_point_v<T> and std::is_scalar_v<T> +[[nodiscard]] FormatResult formatFloat(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, T arg) +{ + switch (std::fpclassify(arg)) { + case FP_INFINITE: + return formatINF(sink, formatMode, formatSpec, std::signbit(arg)); + case FP_NAN: + return formatNAN(sink, formatMode, formatSpec, std::signbit(arg)); + case FP_ZERO: + return formatZero(sink, formatMode, formatSpec, std::signbit(arg)); + default: + break; + } + + auto decimalRepr{jkj::dragonbox::to_decimal(arg)}; + + static_assert(std::is_unsigned_v<decltype(decimalRepr.significand)>); + if constexpr (std::is_same_v<T, double>) { + static_assert(sizeof(decimalRepr.significand) == sizeof(std::uint64_t)); + static_assert(std::is_same_v<decltype(decimalRepr.significand), uint64_t>); + } else { + static_assert(std::is_same_v<T, float>); + static_assert(std::is_same_v<decltype(decimalRepr.significand), uint32_t>); + } + static_assert(std::is_same_v<decltype(decimalRepr.exponent), int32_t>); + static_assert(std::is_same_v<decltype(decimalRepr.is_negative), bool>); + + static_cast<void>(decimalRepr); + + auto uppercaseResult = validateFloatFormatType(formatSpec.formatType); + if (uppercaseResult.isError()) { + return FormatResult{0, uppercaseResult.takeError()}; + } + auto uppercase = uppercaseResult.takeOkay(); + + FormatSpec floatSpec{formatSpec}; + if (floatSpec.formatType == FormatType::Unspecified) { + if (floatSpec.precision != FormatSpec::precisionUnspecified) { + floatSpec.formatType = FormatType::GeneralLower; + } + } + + FormatResult result{0, FudStatus::NotImplemented}; + + using FormatType::FixedLower, FormatType::FixedUpper; + using FormatType::FloatHexLower, FormatType::FloatHexUpper; + using FormatType::GeneralLower, FormatType::GeneralUpper; + using FormatType::ScientificLower, FormatType::ScientificUpper; + + if (floatSpec.formatType == FloatHexLower || floatSpec.formatType == FloatHexUpper) { + } else if (floatSpec.formatType == ScientificLower || floatSpec.formatType == ScientificUpper) { + return formatScientific(sink, floatSpec, decimalRepr, uppercase); + } else if (floatSpec.formatType == FixedLower || floatSpec.formatType == FixedUpper) { + } else if (floatSpec.formatType == GeneralLower || floatSpec.formatType == GeneralUpper) { + } else { + result.status = FudStatus::FormatInvalid; + } + + return result; +} + } // namespace impl template <typename Sink> @@ -1065,31 +1361,21 @@ FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& for template <typename Sink> FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, float arg) { - FormatResult result{0, FudStatus::NotImplemented}; - static_cast<void>(sink); - static_cast<void>(formatMode); - static_cast<void>(formatSpec); - static_cast<void>(arg); - return result; + return impl::formatFloat(sink, formatMode, formatSpec, arg); } template <typename Sink> FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, double arg) { - FormatResult result{0, FudStatus::NotImplemented}; - static_cast<void>(sink); - static_cast<void>(formatMode); - static_cast<void>(formatSpec); - static_cast<void>(arg); - return result; + return impl::formatFloat(sink, formatMode, formatSpec, arg); } template <typename Sink> FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, long double arg) { FormatResult result{0, FudStatus::NotImplemented}; - static_cast<void>(sink); static_cast<void>(formatMode); + static_cast<void>(sink); static_cast<void>(formatSpec); static_cast<void>(arg); return result; |