Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ endif()

target_compile_options(fast_float INTERFACE $<$<AND:$<CXX_COMPILER_ID:MSVC>,$<VERSION_GREATER_EQUAL:$<CXX_COMPILER_VERSION>,19.10>>:/permissive->)

option(FASTFLOAT_TRUNCATED_FRACTION_BENCHMARK
"Build the long truncated-fraction regression benchmark" OFF)
if(FASTFLOAT_TRUNCATED_FRACTION_BENCHMARK)
add_executable(bench_truncated_fraction
benchmarks/bench_truncated_fraction.cpp)
target_link_libraries(bench_truncated_fraction PRIVATE fast_float)
target_compile_features(bench_truncated_fraction PRIVATE cxx_std_17)
endif()

if(FASTFLOAT_INSTALL)
include(CMakePackageConfigHelpers)

Expand Down
183 changes: 183 additions & 0 deletions benchmarks/bench_truncated_fraction.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#include "fast_float/fast_float.h"

#include <array>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>

namespace {

constexpr size_t integer_digits = 770;
constexpr size_t max_digits = 769;
constexpr size_t fraction_digits = 4 * 1024 * 1024;
constexpr size_t direct_iterations = 2048;
constexpr size_t from_chars_iterations = 64;
constexpr size_t zero_suffix_direct_iterations = 8192;
constexpr size_t zero_suffix_from_chars_iterations = 1024;
constexpr double expected_value = 0x0.607b00a417628p-1022;

// The leading digits and exponent take from_chars through digit_comp. The
// final integer digit selects whether parse_mantissa must scan the 4 MiB,
// all-zero fractional suffix.

#if defined(_MSC_VER)
#define FASTFLOAT_BENCH_NOINLINE __declspec(noinline)
#elif defined(__GNUC__) || defined(__clang__)
#define FASTFLOAT_BENCH_NOINLINE __attribute__((noinline))
#else
#define FASTFLOAT_BENCH_NOINLINE
#endif

struct input {
std::string text{};
fast_float::parsed_number_string parsed{};
};

[[noreturn]] void fail(char const *message) {
std::fputs(message, stderr);
std::fputc('\n', stderr);
std::exit(EXIT_FAILURE);
}

std::string make_input(char final_integer_digit) {
std::string result = "8385788696668661046";
result.append(integer_digits - result.size() - 1, '0');
result.push_back(final_integer_digit);
result.push_back('.');
result.append(fraction_digits, '0');
result += "e-1078";
return result;
}

void initialize(input &value, char final_integer_digit) {
value.text = make_input(final_integer_digit);
fast_float::parse_options options;
value.parsed = fast_float::parse_number_string<false>(
value.text.data(), value.text.data() + value.text.size(), options, true);
if (!value.parsed.valid || !value.parsed.too_many_digits ||
value.parsed.integer.len() != integer_digits ||
value.parsed.fraction.len() != fraction_digits) {
fail("unexpected parsed input");
}

double parsed_value = 0;
auto const parsed = fast_float::from_chars(
value.text.data(), value.text.data() + value.text.size(), parsed_value);
if (parsed.ec != std::errc() ||
parsed.ptr != value.text.data() + value.text.size() ||
parsed_value != expected_value) {
fail("unexpected conversion result");
}
}

FASTFLOAT_BENCH_NOINLINE uint64_t
parse_mantissa_once(fast_float::parsed_number_string const &input) {
fast_float::parsed_number_string number = input;
fast_float::bigint result;
size_t digits = 0;
fast_float::parse_mantissa(result, number, max_digits, digits);
bool truncated = false;
return result.hi64(truncated) ^ (uint64_t(digits) << 1) ^ uint64_t(truncated);
}

FASTFLOAT_BENCH_NOINLINE uint64_t from_chars_once(std::string const &input) {
double result = 0;
auto const parsed =
fast_float::from_chars(input.data(), input.data() + input.size(), result);
if (parsed.ec != std::errc() || parsed.ptr != input.data() + input.size()) {
fail("unexpected conversion result");
}

uint64_t bits = 0;
static_assert(sizeof(bits) == sizeof(result), "unexpected double size");
std::memcpy(&bits, &result, sizeof(bits));
return bits;
}

inline void do_not_optimize(uint64_t value) {
#if defined(__GNUC__) || defined(__clang__)
asm volatile("" : : "r"(value) : "memory");
#else
volatile uint64_t sink = value;
(void)sink;
#endif
}

template <typename Function>
double measure(std::array<input, 2> const &inputs, size_t iterations,
Function function) {
uint64_t sink = 0;
auto const start = std::chrono::steady_clock::now();
for (size_t index = 0; index < iterations; ++index) {
sink += function(inputs[index & 1]);
do_not_optimize(sink);
}
auto const finish = std::chrono::steady_clock::now();
auto const elapsed =
std::chrono::duration_cast<std::chrono::nanoseconds>(finish - start)
.count();
return double(elapsed) / double(iterations);
}

void print_measurement(double nanoseconds_per_operation) {
std::printf("{\"metric\":\"ns/op\",\"value\":%.17g}\n",
nanoseconds_per_operation);
}

} // namespace

int main(int argc, char **argv) {
if (argc != 2) {
std::fputs(
"usage: bench_truncated_fraction "
"{parse_mantissa|from_chars|parse_mantissa_zero_suffix|"
"from_chars_zero_suffix}\n",
stderr);
return EXIT_FAILURE;
}

bool direct = false;
bool zero_suffix = false;
if (std::strcmp(argv[1], "parse_mantissa") == 0) {
direct = true;
} else if (std::strcmp(argv[1], "from_chars") == 0) {
direct = false;
} else if (std::strcmp(argv[1], "parse_mantissa_zero_suffix") == 0) {
direct = true;
zero_suffix = true;
} else if (std::strcmp(argv[1], "from_chars_zero_suffix") == 0) {
zero_suffix = true;
} else {
std::fputs(
"usage: bench_truncated_fraction "
"{parse_mantissa|from_chars|parse_mantissa_zero_suffix|"
"from_chars_zero_suffix}\n",
stderr);
return EXIT_FAILURE;
}

std::array<input, 2> inputs{};
initialize(inputs[0], zero_suffix ? '0' : '1');
initialize(inputs[1], zero_suffix ? '0' : '2');
size_t const iterations =
zero_suffix
? (direct ? zero_suffix_direct_iterations
: zero_suffix_from_chars_iterations)
: (direct ? direct_iterations : from_chars_iterations);

if (direct) {
print_measurement(measure(inputs, iterations,
[](input const &value) {
return parse_mantissa_once(value.parsed);
}));
} else {
print_measurement(measure(inputs, iterations,
[](input const &value) {
return from_chars_once(value.text);
}));
}
return EXIT_SUCCESS;
}
5 changes: 3 additions & 2 deletions include/fast_float/digit_comparison.h
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,9 @@ parse_mantissa(bigint &result, parsed_number_string_t<UC> &num,
// add the temporary value, then check if we've truncated any digits
add_native(result, limb(powers_of_ten_uint64[counter]), value);
bool truncated = is_truncated(p, pend);
if (num.fraction.ptr != nullptr) {
truncated |= is_truncated(num.fraction);
// A nonzero integer suffix already determines the rounding direction.
if (!truncated && num.fraction.ptr != nullptr) {
truncated = is_truncated(num.fraction);
}
if (truncated) {
round_up_bigint(result, digits);
Expand Down
92 changes: 92 additions & 0 deletions tests/basictest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,98 @@ TEST_CASE("double.inf") {
std::errc::result_out_of_range);
}

TEST_CASE("truncated integer mantissa") {
constexpr size_t max_digits = fast_float::binary_format<double>::max_digits();
constexpr size_t integer_digits = max_digits + 1;
constexpr size_t fraction_length = 1024;

auto make_mantissa = [=](char final_integer_digit,
char final_fraction_digit) {
std::string input = "1234567890123456789";
input.append(integer_digits - input.size() - 1, '0');
input.push_back(final_integer_digit);
input.push_back('.');
input.append(fraction_length - 1, '0');
input.push_back(final_fraction_digit);
return input;
};

auto parse_mantissa = [=](std::string const &input,
fast_float::bigint &result) -> size_t {
fast_float::parse_options options;
auto number = fast_float::parse_number_string<false>(
input.data(), input.data() + input.size(), options, true);
CHECK(number.valid);
CHECK(number.integer.len() == integer_digits);
CHECK(number.fraction.len() == fraction_length);

size_t digits = 0;
fast_float::parse_mantissa(result, number, max_digits, digits);
return digits;
};

auto const nonzero_integer_zero_fraction = make_mantissa('1', '0');
auto const nonzero_integer_nonzero_fraction = make_mantissa('1', '1');
fast_float::bigint nonzero_integer_zero_result;
fast_float::bigint nonzero_integer_nonzero_result;
CHECK(parse_mantissa(nonzero_integer_zero_fraction,
nonzero_integer_zero_result) == max_digits + 1);
CHECK(parse_mantissa(nonzero_integer_nonzero_fraction,
nonzero_integer_nonzero_result) == max_digits + 1);
CHECK(nonzero_integer_zero_result.compare(nonzero_integer_nonzero_result) ==
0);

auto const zero_integer_zero_fraction = make_mantissa('0', '0');
auto const zero_integer_nonzero_fraction = make_mantissa('0', '1');
fast_float::bigint zero_integer_zero_result;
fast_float::bigint zero_integer_nonzero_result;
CHECK(parse_mantissa(zero_integer_zero_fraction,
zero_integer_zero_result) == max_digits);
CHECK(parse_mantissa(zero_integer_nonzero_fraction,
zero_integer_nonzero_result) == max_digits + 1);
CHECK(zero_integer_zero_result.compare(zero_integer_nonzero_result) < 0);

auto make_exact_conversion_input = [=](char final_fraction_digit) {
std::string input = "8385788696668661046";
input.append(integer_digits - input.size() - 1, '0');
input.push_back('1');
input.push_back('.');
input.append(fraction_length - 1, '0');
input.push_back(final_fraction_digit);
input += "e-1078";
return input;
};

auto const exact_input = make_exact_conversion_input('0');
fast_float::parse_options options;
auto const number = fast_float::parse_number_string<false>(
exact_input.data(), exact_input.data() + exact_input.size(), options,
true);
REQUIRE(number.too_many_digits);
REQUIRE(number.integer.len() == integer_digits);
REQUIRE(number.fraction.len() == fraction_length);
auto const approximate =
fast_float::compute_float<fast_float::binary_format<double>>(
number.exponent, number.mantissa);
auto const next = fast_float::compute_float<fast_float::binary_format<double>>(
number.exponent, number.mantissa + 1);
REQUIRE(approximate != next);
REQUIRE(fast_float::compute_error<fast_float::binary_format<double>>(
number.exponent, number.mantissa)
.power2 < 0);

for (char final_fraction_digit = '0'; final_fraction_digit <= '1';
++final_fraction_digit) {
auto const input = make_exact_conversion_input(final_fraction_digit);
double value = 0;
auto const result = fast_float::from_chars(
input.data(), input.data() + input.size(), value);
CHECK(result.ec == std::errc());
CHECK(result.ptr == input.data() + input.size());
CHECK(value == 0x0.607b00a417628p-1022);
}
}

TEST_CASE("double.general") {
verify("0.95000000000000000000", 0.95);
verify("22250738585072012e-324",
Expand Down