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
196 changes: 196 additions & 0 deletions benchmarks/benchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#endif
#include "counters/event_counter.h"
#include <algorithm>
#include <array>
#include "fast_float/fast_float.h"
#include <chrono>
#include <climits>
Expand Down Expand Up @@ -226,7 +227,202 @@ void fileload(std::string filename) {
process(lines, volume);
}

namespace {

constexpr size_t truncated_fraction_integer_digits =
fast_float::binary_format<double>::max_digits() + 1;
constexpr size_t truncated_fraction_max_digits =
fast_float::binary_format<double>::max_digits();
constexpr size_t truncated_fraction_digits = 4 * 1024 * 1024;
constexpr size_t truncated_fraction_batches = 9;
constexpr size_t truncated_fraction_zero_batches = 17;
constexpr size_t truncated_fraction_direct_iterations = 2048;
constexpr size_t truncated_fraction_direct_zero_iterations = 8192;
constexpr size_t truncated_fraction_from_chars_iterations = 256;
constexpr size_t truncated_fraction_from_chars_zero_iterations = 512;
constexpr double truncated_fraction_expected_value = 0x0.607b00a417628p-1022;

#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 truncated_fraction_input {
std::string text{};
fast_float::parsed_number_string parsed{};
};

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

void truncated_fraction_usage() {
std::fputs("usage: realbenchmark --truncated-fraction "
"{parse_mantissa|from_chars} {nonzero|zero}\n",
stderr);
}

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

void initialize_truncated_fraction_input(truncated_fraction_input &input,
char final_integer_digit) {
input.text = make_truncated_fraction_input(final_integer_digit);
fast_float::parse_options options;
input.parsed = fast_float::parse_number_string<false>(
input.text.data(), input.text.data() + input.text.size(), options, true);
if (!input.parsed.valid || !input.parsed.too_many_digits ||
input.parsed.integer.len() != truncated_fraction_integer_digits ||
input.parsed.fraction.len() != truncated_fraction_digits) {
truncated_fraction_fail("unexpected parsed input");
}

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

FASTFLOAT_BENCH_NOINLINE uint64_t
parse_mantissa_once(truncated_fraction_input const &input) {
fast_float::parsed_number_string number = input.parsed;
fast_float::bigint result;
size_t digits = 0;
fast_float::parse_mantissa(result, number, truncated_fraction_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(truncated_fraction_input const &input) {
double result = 0;
auto const parsed = fast_float::from_chars(
input.text.data(), input.text.data() + input.text.size(), result);
if (parsed.ec != std::errc() ||
parsed.ptr != input.text.data() + input.text.size()) {
truncated_fraction_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_truncated_fraction(
std::array<truncated_fraction_input, 2> const &inputs, size_t iterations,
size_t batches, Function function) {
std::array<double, truncated_fraction_zero_batches> samples{};
for (size_t batch = 0; batch < batches; ++batch) {
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]);
}
auto const finish = std::chrono::steady_clock::now();
do_not_optimize(sink);
samples[batch] =
double(
std::chrono::duration_cast<std::chrono::nanoseconds>(finish - start)
.count()) /
double(iterations);
}
std::sort(samples.begin(), samples.begin() + batches);
return samples[batches / 2];
}

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

int run_truncated_fraction_benchmark(char const *operation,
char const *integer_suffix) {
bool direct = false;
if (std::strcmp(operation, "parse_mantissa") == 0) {
direct = true;
} else if (std::strcmp(operation, "from_chars") != 0) {
truncated_fraction_usage();
return EXIT_FAILURE;
}

bool zero_suffix = false;
if (std::strcmp(integer_suffix, "zero") == 0) {
zero_suffix = true;
} else if (std::strcmp(integer_suffix, "nonzero") != 0) {
truncated_fraction_usage();
return EXIT_FAILURE;
}

std::array<truncated_fraction_input, 2> inputs{};
initialize_truncated_fraction_input(inputs[0], zero_suffix ? '0' : '1');
initialize_truncated_fraction_input(inputs[1], zero_suffix ? '0' : '2');

// The zero-suffix route retains the long fractional scan. Use larger batches
// and more independent samples there so that its unchanged path has a precise
// check.
size_t const iterations =
zero_suffix ? (direct ? truncated_fraction_direct_zero_iterations
: truncated_fraction_from_chars_zero_iterations)
: (direct ? truncated_fraction_direct_iterations
: truncated_fraction_from_chars_iterations);
size_t const batches = zero_suffix ? truncated_fraction_zero_batches
: truncated_fraction_batches;

if (direct) {
print_truncated_fraction_measurement(measure_truncated_fraction(
inputs, iterations, batches, [](truncated_fraction_input const &input) {
return parse_mantissa_once(input);
}));
} else {
print_truncated_fraction_measurement(measure_truncated_fraction(
inputs, iterations, batches, [](truncated_fraction_input const &input) {
return from_chars_once(input);
}));
}
return EXIT_SUCCESS;
}

#undef FASTFLOAT_BENCH_NOINLINE

} // namespace

int main(int argc, char **argv) {
if (argc > 1 && std::strcmp(argv[1], "--truncated-fraction") == 0) {
if (argc != 4) {
truncated_fraction_usage();
return EXIT_FAILURE;
}
return run_truncated_fraction_benchmark(argv[2], argv[3]);
}

if (collector.has_events()) {
std::cout << "# Using hardware counters" << std::endl;
} else {
Expand Down
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
93 changes: 93 additions & 0 deletions tests/basictest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,99 @@ 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