Skip to content

Commit 98d01d3

Browse files
committed
Add fixed format support to float
1 parent ddbc414 commit 98d01d3

File tree

2 files changed

+51
-22
lines changed

2 files changed

+51
-22
lines changed

test/float-check.cc

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
#include <vector>
1313

1414
#include "dragonbox/dragonbox_to_chars.h"
15-
#include "fmt/base.h"
15+
#include "fmt/format.h"
1616
#include "zmij.h"
1717

1818
auto main() -> int {
@@ -54,7 +54,27 @@ auto main() -> int {
5454
memcpy(&value, &bits, sizeof(float));
5555

5656
zmij::write(actual, sizeof(actual), value);
57-
*jkj::dragonbox::to_chars(value, expected) = '\0';
57+
auto dec = jkj::dragonbox::to_decimal(fabs(value));
58+
int exp = dec.exponent + fmt::detail::count_digits(dec.significand) - 1;
59+
if (exp >= -4 && exp < 7) {
60+
// Dragonbox only support exponential format so handle fixed
61+
// ourselves.
62+
std::string str = fmt::format("{}", dec.significand);
63+
64+
int pos = int(str.size()) + dec.exponent;
65+
if (pos <= 0)
66+
str = "0." + std::string(-pos, '0') + str;
67+
else if (pos < int(str.size()))
68+
str.insert(pos, ".");
69+
else
70+
str.append(dec.exponent, '0');
71+
72+
char* buffer = expected;
73+
if (value < 0) *buffer++ = '-';
74+
strcpy(buffer, str.c_str());
75+
} else {
76+
*jkj::dragonbox::to_chars(value, expected) = '\0';
77+
}
5878

5979
if (strcmp(actual, expected) == 0) continue;
6080
if (strcmp(actual, "0") == 0 && strcmp(expected, "0e0") == 0) continue;
@@ -63,7 +83,8 @@ auto main() -> int {
6383

6484
++num_errors;
6585
if (!has_errors) {
66-
fmt::print("\nOutput mismatch: {} != {}\n", actual, expected);
86+
fmt::print("\nOutput mismatch: {} != {} ({})\n", actual, expected,
87+
exp);
6788
has_errors = true;
6889
}
6990
}

zmij.cc

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -504,12 +504,10 @@ inline auto read8(char* buffer) noexcept -> uint64_t {
504504
// and removes trailing zeros.
505505
auto write_significand9(char* buffer, uint32_t value, bool has9digits) noexcept
506506
-> char* {
507-
char* start = buffer;
508507
buffer = write_if(buffer, value / 100'000'000, has9digits);
509508
uint64_t bcd = to_bcd8(value % 100'000'000);
510509
write8(buffer, bcd | zeros);
511-
buffer += count_trailing_nonzeros(bcd);
512-
return buffer - int(buffer - start == 1);
510+
return buffer + count_trailing_nonzeros(bcd);
513511
}
514512

515513
// Writes a significand consisting of up to 17 decimal digits (16-17 for
@@ -870,29 +868,38 @@ ZMIJ_INLINE auto to_decimal_normal(UInt bin_sig, int64_t raw_exp,
870868
return to_decimal_schubfach(bin_sig, bin_exp, regular);
871869
}
872870

871+
template <int num_bits>
873872
auto write_fixed(char* buffer, uint64_t dec_sig, int dec_exp, bool has17digits,
874873
long long dec_sig_div10) noexcept -> char* {
875874
if (dec_exp < 0) {
876875
memcpy(buffer, "0.0000000", 8);
877-
buffer = write_significand17(buffer + 1 - dec_exp, dec_sig, has17digits,
878-
dec_sig_div10);
876+
buffer = num_bits == 64 ? write_significand17(buffer + 1 - dec_exp, dec_sig,
877+
has17digits, dec_sig_div10)
878+
: write_significand9(buffer + 1 - dec_exp, dec_sig,
879+
has17digits);
879880
*buffer = '\0';
880881
return buffer;
881882
}
882883

883884
// Avoid reading uninitialized memory (would be unnecessary in asm).
884-
write8(buffer + 16, 0);
885+
write8(buffer + (num_bits == 64 ? 16 : 7), 0);
885886

886887
char* start = buffer;
887-
buffer = write_significand17(buffer, dec_sig, has17digits, dec_sig_div10);
888+
buffer = num_bits == 64 ? write_significand17(buffer, dec_sig, has17digits,
889+
dec_sig_div10)
890+
: write_significand9(buffer, dec_sig, has17digits);
888891

889892
// Branchless move to make space for the '.' without OOB accesses.
890893
char* part1 = start + dec_exp + (dec_exp < 2);
891894
char* part2 = part1 + (dec_exp < 2) + (dec_exp < 9 ? 7 : 0);
892-
uint64_t value1 = read8(part1);
893-
uint64_t value2 = read8(part2);
894-
write8(part1 + 1, value1);
895-
write8(part2 + 1, value2);
895+
if (num_bits == 64) {
896+
uint64_t value1 = read8(part1);
897+
uint64_t value2 = read8(part2);
898+
write8(part1 + 1, value1);
899+
write8(part2 + 1, value2);
900+
} else {
901+
write8(part1 + 1, read8(part1));
902+
}
896903

897904
char* dot = start + dec_exp + 1;
898905
*dot = '.';
@@ -958,22 +965,23 @@ auto write(Float value, char* buffer) noexcept -> char* {
958965
bin_sig != 0);
959966
}
960967
int dec_exp = dec.exp;
961-
constexpr uint64_t threshold = uint64_t(traits::num_bits == 64 ? 1e16 : 1e8);
962-
bool extra_digit = dec.sig >= threshold;
968+
bool extra_digit = dec.sig >= uint64_t(traits::num_bits == 64 ? 1e16 : 1e8);
963969
dec_exp += traits::max_digits10 - 2 + extra_digit;
970+
if (traits::num_bits == 32 && dec.sig < uint32_t(1e7)) [[ZMIJ_UNLIKELY]] {
971+
dec.sig *= 10;
972+
--dec_exp;
973+
}
964974

965975
// Write significand.
976+
if (dec_exp >= -4 && dec_exp < compute_dec_exp(traits::digits + 1, true)) {
977+
return write_fixed<traits::num_bits>(buffer, dec.sig, dec_exp, extra_digit,
978+
dec.sig_div10);
979+
}
966980
char* start = buffer;
967981
if (traits::num_bits == 64) {
968-
if (dec_exp >= -4 && dec_exp < compute_dec_exp(traits::digits + 1, true))
969-
return write_fixed(buffer, dec.sig, dec_exp, extra_digit, dec.sig_div10);
970982
buffer =
971983
write_significand17(buffer + 1, dec.sig, extra_digit, dec.sig_div10);
972984
} else {
973-
if (dec.sig < uint32_t(1e7)) [[ZMIJ_UNLIKELY]] {
974-
dec.sig *= 10;
975-
--dec_exp;
976-
}
977985
buffer = write_significand9(buffer + 1, dec.sig, extra_digit);
978986
}
979987
start[0] = start[1];

0 commit comments

Comments
 (0)