summaryrefslogtreecommitdiff
path: root/include/fud_format.hpp
diff options
context:
space:
mode:
authorDominick Allen <djallen@librehumanitas.org>2024-10-27 20:13:52 -0500
committerDominick Allen <djallen@librehumanitas.org>2024-10-27 20:13:52 -0500
commitcbf3ad2b284a9e79ef5df564be6b16e8e746cb2b (patch)
treef3c3a1cf9c1286144d002611b209f63541b14426 /include/fud_format.hpp
parentb8345246dcc2121bcb6d1515a9341789de20199f (diff)
Setup float formatting.
Diffstat (limited to 'include/fud_format.hpp')
-rw-r--r--include/fud_format.hpp390
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;