mirror of
https://github.com/fastfloat/fast_float.git
synced 2026-10-29 02:17:15 +08:00
164 lines
4.8 KiB
C++
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;
|
|
}
|