fast_float/tests/exhaustive16_midpoint.cpp

164 lines
4.8 KiB
C++

// For std::float16_t and std::bfloat16_t, parse the exact decimal expansion
// of every finite value and of every midpoint between adjacent values, plus
// strings just above and below each midpoint and midpoints padded past 19
// digits. The expected bits follow from the bit pattern that generated the
// string, so no C library reference is needed.
#include "fast_float/fast_float.h"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdfloat>
#include <string>
#include <system_error>
namespace {
// Decimal digit strings, most significant digit first, no leading zeros.
std::string times(std::string const &digits, unsigned factor) {
std::string out;
unsigned carry = 0;
for (size_t i = digits.size(); i-- > 0;) {
unsigned v = static_cast<unsigned>(digits[i] - '0') * factor + carry;
out.push_back(static_cast<char>('0' + v % 10));
carry = v / 10;
}
while (carry != 0) {
out.push_back(static_cast<char>('0' + carry % 10));
carry /= 10;
}
return std::string(out.rbegin(), out.rend());
}
std::string minus_one(std::string digits) {
size_t i = digits.size();
while (i-- > 0) {
if (digits[i] != '0') {
digits[i] = static_cast<char>(digits[i] - 1);
break;
}
digits[i] = '9';
}
return digits;
}
// num * 2^exp2 written as "<digits>e<exponent>" with an exact expansion.
struct Decimal {
std::string digits;
int exponent;
std::string str() const { return digits + "e" + std::to_string(exponent); }
Decimal above() const { return {digits + "1", exponent - 1}; }
Decimal below() const { return {minus_one(digits) + "9", exponent - 1}; }
Decimal padded(size_t count) const {
return {digits + std::string(count, '0'),
exponent - static_cast<int>(count)};
}
};
Decimal exact_decimal(uint64_t num, int exp2) {
std::string digits = std::to_string(num);
if (exp2 >= 0) {
for (int i = 0; i < exp2; i++) {
digits = times(digits, 2);
}
return {digits, 0};
}
for (int i = 0; i < -exp2; i++) {
digits = times(digits, 5);
}
return {digits, exp2};
}
template <typename T> struct Layout;
template <> struct Layout<std::float16_t> {
static constexpr char const *name = "float16";
static constexpr int p = 10;
static constexpr int bias = 25; // value = mantissa * 2^(field - bias)
static constexpr uint16_t infinity = 0x7C00;
};
template <> struct Layout<std::bfloat16_t> {
static constexpr char const *name = "bfloat16";
static constexpr int p = 7;
static constexpr int bias = 134;
static constexpr uint16_t infinity = 0x7F80;
};
struct Failure {
long count = 0;
long checked = 0;
};
template <typename T>
void expect(Failure &f, std::string const &s, uint16_t want) {
T value{};
auto r = fast_float::from_chars(s.data(), s.data() + s.size(), value);
uint16_t got = 0;
std::memcpy(&got, &value, sizeof(got));
bool zero_or_inf = want == 0 || want == Layout<T>::infinity;
std::errc want_ec =
zero_or_inf ? std::errc::result_out_of_range : std::errc();
f.checked++;
if (got != want || r.ec != want_ec || r.ptr != s.data() + s.size()) {
f.count++;
if (f.count <= 20) {
std::printf(
"%s: \"%s\" parsed to 0x%04x (ec %d), expected 0x%04x (ec %d)\n",
Layout<T>::name, s.c_str(), got, static_cast<int>(r.ec), want,
static_cast<int>(want_ec));
}
}
}
template <typename T> Failure sweep() {
Failure f;
constexpr int p = Layout<T>::p;
constexpr uint16_t mantissa_mask = static_cast<uint16_t>((1u << p) - 1);
for (uint32_t b = 0; b < Layout<T>::infinity; b++) {
uint16_t bits = static_cast<uint16_t>(b);
int field = bits >> p;
uint64_t num = bits & mantissa_mask;
int exp2 = (field == 0 ? 1 : field) - Layout<T>::bias;
if (field != 0) {
num |= uint64_t(1) << p;
}
if (num != 0) {
Decimal d = exact_decimal(num, exp2);
expect<T>(f, d.str(), bits);
expect<T>(f, d.padded(21).str(), bits);
}
// The tie between bits and bits + 1 is (2 num + 1) * 2^(exp2 - 1); the
// last one (bits + 1 == infinity) is the overflow threshold.
Decimal tie = exact_decimal(2 * num + 1, exp2 - 1);
uint16_t lower = bits;
uint16_t upper = static_cast<uint16_t>(bits + 1);
uint16_t even = (bits & 1) ? upper : lower;
expect<T>(f, tie.str(), even);
expect<T>(f, tie.padded(21).str(), even);
expect<T>(f, tie.above().str(), upper);
expect<T>(f, tie.below().str(), lower);
}
return f;
}
} // namespace
int main() {
Failure f16 = sweep<std::float16_t>();
std::printf("float16: %ld strings, %ld failures\n", f16.checked, f16.count);
Failure bf16 = sweep<std::bfloat16_t>();
std::printf("bfloat16: %ld strings, %ld failures\n", bf16.checked,
bf16.count);
if (f16.count != 0 || bf16.count != 0) {
return EXIT_FAILURE;
}
std::printf("all ok\n");
return EXIT_SUCCESS;
}