From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 2153) id CB30D3858D35; Tue, 1 Nov 2022 12:43:30 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org CB30D3858D35 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1667306614; bh=nUdRSJg4hjtIrvH9NX0NnFIDT9hhFoj1zaMOVX8z/p4=; h=From:To:Subject:Date:From; b=n0aAEyaMACIx116BvoOc41hI2hn9upanc9tvRNSrDZgaHyST62AAuJkX7hO14jGd5 Z1vq/CFBIN4TAHcx06OIbP09JofyHM3iqDPoP9dA5dmwWIeNhpLYgkQSZqCTC3m5rG 6AnKr2yMyIyVL14oA46HYmBAxrhwjcNlLYX++xro= MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Type: text/plain; charset="utf-8" From: Jakub Jelinek To: gcc-cvs@gcc.gnu.org, libstdc++-cvs@gcc.gnu.org Subject: [gcc r13-3592] libstdc++: std::from_chars std::{,b}float16_t support X-Act-Checkin: gcc X-Git-Author: Jakub Jelinek X-Git-Refname: refs/heads/master X-Git-Oldrev: 0ae26533b3e26866f4130d40ec0659c273552643 X-Git-Newrev: 81f98afa22815e7397714caf6fa0fc815803ebaa Message-Id: <20221101124334.CB30D3858D35@sourceware.org> Date: Tue, 1 Nov 2022 12:43:30 +0000 (GMT) List-Id: https://gcc.gnu.org/g:81f98afa22815e7397714caf6fa0fc815803ebaa commit r13-3592-g81f98afa22815e7397714caf6fa0fc815803ebaa Author: Jakub Jelinek Date: Tue Nov 1 13:41:57 2022 +0100 libstdc++: std::from_chars std::{,b}float16_t support The following patch adds std::from_chars support, similarly to the previous std::to_chars patch through APIs that use float instead of the 16-bit floating point formats as container. The patch uses the fast_float library and doesn't need any changes to it, like the previous patch it introduces wrapper classes around float that represent the float holding float16_t or bfloat16_t value, and specializes binary_format etc. from fast_float for these classes. The new test verifies exhaustively to_chars and from_chars afterward results in the original value (except for nans) in all the fmt cases. 2022-11-01 Jakub Jelinek * include/std/charconv (__from_chars_float16_t, __from_chars_bfloat16_t): Declare. (from_chars): Add _Float16 and __gnu_cxx::__bfloat16_t overloads. * config/abi/pre/gnu.ver (GLIBCXX_3.4.31): Export _ZSt22__from_chars_float16_tPKcS0_RfSt12chars_format and _ZSt23__from_chars_bfloat16_tPKcS0_RfSt12chars_format. * src/c++17/floating_from_chars.cc (fast_float::floating_type_float16_t, fast_float::floating_type_bfloat16_t): New classes. (fast_float::binary_format, fast_float::binary_format): New specializations. (fast_float::to_float, fast_float::to_float, fast_float::to_extended, fast_float::to_extended): Likewise. (fast_float::from_chars_16): New template function. (__floating_from_chars_hex): Allow instantiation with fast_float::floating_type_{,b}float16_t. (from_chars): Formatting fixes for float/double/long double overloads. (__from_chars_float16_t, __from_chars_bfloat16_t): New functions. * testsuite/20_util/to_chars/float16_c++23.cc: New test. Diff: --- libstdc++-v3/config/abi/pre/gnu.ver | 2 + libstdc++-v3/include/std/charconv | 42 +++ libstdc++-v3/src/c++17/floating_from_chars.cc | 400 +++++++++++++++++++-- .../testsuite/20_util/to_chars/float16_c++23.cc | 76 ++++ 4 files changed, 491 insertions(+), 29 deletions(-) diff --git a/libstdc++-v3/config/abi/pre/gnu.ver b/libstdc++-v3/config/abi/pre/gnu.ver index 729e0cf9391..1c714fba0ae 100644 --- a/libstdc++-v3/config/abi/pre/gnu.ver +++ b/libstdc++-v3/config/abi/pre/gnu.ver @@ -2448,6 +2448,8 @@ GLIBCXX_3.4.31 { _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE15_M_replace_cold*; _ZSt20__to_chars_float16_tPcS_fSt12chars_format; _ZSt21__to_chars_bfloat16_tPcS_fSt12chars_format; + _ZSt22__from_chars_float16_tPKcS0_RfSt12chars_format; + _ZSt23__from_chars_bfloat16_tPKcS0_RfSt12chars_format; } GLIBCXX_3.4.30; # Symbols in the support library (libsupc++) have their own tag. diff --git a/libstdc++-v3/include/std/charconv b/libstdc++-v3/include/std/charconv index e391a278087..e9bf953cdcc 100644 --- a/libstdc++-v3/include/std/charconv +++ b/libstdc++-v3/include/std/charconv @@ -673,6 +673,32 @@ namespace __detail from_chars(const char* __first, const char* __last, long double& __value, chars_format __fmt = chars_format::general) noexcept; + // Library routines for 16-bit extended floating point formats + // using float as interchange format. + from_chars_result + __from_chars_float16_t(const char* __first, const char* __last, + float& __value, + chars_format __fmt = chars_format::general) noexcept; + from_chars_result + __from_chars_bfloat16_t(const char* __first, const char* __last, + float& __value, + chars_format __fmt = chars_format::general) noexcept; + +#if defined(__STDCPP_FLOAT16_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) \ + && defined(__cpp_lib_to_chars) + inline from_chars_result + from_chars(const char* __first, const char* __last, _Float16& __value, + chars_format __fmt = chars_format::general) noexcept + { + float __val; + from_chars_result __res + = __from_chars_float16_t(__first, __last, __val, __fmt); + if (__res.ec == errc{}) + __value = __val; + return __res; + } +#endif + #if defined(__STDCPP_FLOAT32_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) inline from_chars_result from_chars(const char* __first, const char* __last, _Float32& __value, @@ -711,6 +737,22 @@ namespace __detail return __res; } #endif + +#if defined(__STDCPP_BFLOAT16_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) \ + && defined(__cpp_lib_to_chars) + inline from_chars_result + from_chars(const char* __first, const char* __last, + __gnu_cxx::__bfloat16_t & __value, + chars_format __fmt = chars_format::general) noexcept + { + float __val; + from_chars_result __res + = __from_chars_bfloat16_t(__first, __last, __val, __fmt); + if (__res.ec == errc{}) + __value = __val; + return __res; + } +#endif #endif #if defined __cpp_lib_to_chars diff --git a/libstdc++-v3/src/c++17/floating_from_chars.cc b/libstdc++-v3/src/c++17/floating_from_chars.cc index 5d2a931d5dd..a25ac5ce3aa 100644 --- a/libstdc++-v3/src/c++17/floating_from_chars.cc +++ b/libstdc++-v3/src/c++17/floating_from_chars.cc @@ -75,6 +75,272 @@ extern "C" __ieee128 __strtoieee128(const char*, char**); namespace { # include "fast_float/fast_float.h" + +namespace fast_float +{ + + // Wrappers around float for std::{,b}float16_t promoted to float. + struct floating_type_float16_t + { + float* x; + uint16_t bits; + }; + struct floating_type_bfloat16_t + { + float* x; + uint16_t bits; + }; + + template<> + constexpr int + binary_format::mantissa_explicit_bits() + { return 10; } + + template<> + constexpr int + binary_format::mantissa_explicit_bits() + { return 7; } + + // 10 bits of stored mantissa, pow(5,q) <= 0x4p+10 implies q <= 5 + template<> + constexpr int + binary_format::max_exponent_round_to_even() + { return 5; } + + // 7 bits of stored mantissa, pow(5,q) <= 0x4p+7 implies q <= 3 + template<> + constexpr int + binary_format::max_exponent_round_to_even() + { return 3; } + + // 10 bits of stored mantissa, pow(5,-q) < 0x1p+64 / 0x1p+11 implies q >= -22 + template<> + constexpr int + binary_format::min_exponent_round_to_even() + { return -22; } + + // 7 bits of stored mantissa, pow(5,-q) < 0x1p+64 / 0x1p+8 implies q >= -24 + template<> + constexpr int + binary_format::min_exponent_round_to_even() + { return -24; } + + template<> + constexpr int + binary_format::minimum_exponent() + { return -15; } + + template<> + constexpr int + binary_format::minimum_exponent() + { return -127; } + + template<> + constexpr int + binary_format::infinite_power() + { return 0x1F; } + + template<> + constexpr int + binary_format::infinite_power() + { return 0xFF; } + + template<> + constexpr int + binary_format::sign_index() + { return 15; } + + template<> + constexpr int + binary_format::sign_index() + { return 15; } + + template<> + constexpr int + binary_format::largest_power_of_ten() + { return 4; } + + template<> + constexpr int + binary_format::largest_power_of_ten() + { return 38; } + + template<> + constexpr int + binary_format::smallest_power_of_ten() + { return -27; } + + template<> + constexpr int + binary_format::smallest_power_of_ten() + { return -60; } + + template<> + constexpr size_t + binary_format::max_digits() + { return 22; } + + template<> + constexpr size_t + binary_format::max_digits() + { return 98; } + + // negative_digit_comp converts adjusted_mantissa to the (originally only) + // floating type and immediately back with slight tweaks (e.g. explicit + // leading bit instead of implicit for normals). + // Avoid going through the floating point type. + template<> + fastfloat_really_inline void + to_float(bool negative, adjusted_mantissa am, + floating_type_float16_t &value) + { + constexpr int mantissa_bits + = binary_format::mantissa_explicit_bits(); + value.bits = (am.mantissa + | (uint16_t(am.power2) << mantissa_bits) + | (negative ? 0x8000 : 0)); + } + + template<> + fastfloat_really_inline void + to_float(bool negative, adjusted_mantissa am, + floating_type_bfloat16_t &value) + { + constexpr int mantissa_bits + = binary_format::mantissa_explicit_bits(); + value.bits = (am.mantissa + | (uint16_t(am.power2) << mantissa_bits) + | (negative ? 0x8000 : 0)); + } + + template <> + fastfloat_really_inline adjusted_mantissa + to_extended(floating_type_float16_t value) noexcept + { + adjusted_mantissa am; + constexpr int mantissa_bits + = binary_format::mantissa_explicit_bits(); + int32_t bias + = (mantissa_bits + - binary_format::minimum_exponent()); + constexpr uint16_t exponent_mask = 0x7C00; + constexpr uint16_t mantissa_mask = 0x03FF; + constexpr uint16_t hidden_bit_mask = 0x0400; + if ((value.bits & exponent_mask) == 0) { + // denormal + am.power2 = 1 - bias; + am.mantissa = value.bits & mantissa_mask; + } else { + // normal + am.power2 = int32_t((value.bits & exponent_mask) >> mantissa_bits); + am.power2 -= bias; + am.mantissa = (value.bits & mantissa_mask) | hidden_bit_mask; + } + return am; + } + + template <> + fastfloat_really_inline adjusted_mantissa + to_extended(floating_type_bfloat16_t value) noexcept + { + adjusted_mantissa am; + constexpr int mantissa_bits + = binary_format::mantissa_explicit_bits(); + int32_t bias + = (mantissa_bits + - binary_format::minimum_exponent()); + constexpr uint16_t exponent_mask = 0x7F80; + constexpr uint16_t mantissa_mask = 0x007F; + constexpr uint16_t hidden_bit_mask = 0x0080; + if ((value.bits & exponent_mask) == 0) { + // denormal + am.power2 = 1 - bias; + am.mantissa = value.bits & mantissa_mask; + } else { + // normal + am.power2 = int32_t((value.bits & exponent_mask) >> mantissa_bits); + am.power2 -= bias; + am.mantissa = (value.bits & mantissa_mask) | hidden_bit_mask; + } + return am; + } + + // Like fast_float.h from_chars_advanced, but for 16-bit float. + template + from_chars_result + from_chars_16(const char* first, const char* last, T &value, + chars_format fmt) noexcept + { + parse_options options{fmt}; + + from_chars_result answer; + if (first == last) + { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + + parsed_number_string pns = parse_number_string(first, last, options); + if (!pns.valid) + return detail::parse_infnan(first, last, *value.x); + + answer.ec = std::errc(); + answer.ptr = pns.lastmatch; + + adjusted_mantissa am + = compute_float>(pns.exponent, pns.mantissa); + if (pns.too_many_digits && am.power2 >= 0) + { + if (am != compute_float>(pns.exponent, + pns.mantissa + 1)) + am = compute_error>(pns.exponent, pns.mantissa); + } + + // If we called compute_float>(pns.exponent, pns.mantissa) + // and we have an invalid power (am.power2 < 0), + // then we need to go the long way around again. This is very uncommon. + if (am.power2 < 0) + am = digit_comp(pns, am); + + if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) + || am.power2 == binary_format::infinite_power()) + { + // In case of over/underflow, return result_out_of_range and don't + // modify value, as per [charconv.from.chars]/1. Note that LWG 3081 wants + // to modify value in this case too. + answer.ec = std::errc::result_out_of_range; + return answer; + } + + // Transform the {,b}float16_t to float32_t before to_float. + if constexpr (std::is_same_v) + { + if (am.power2 == 0) + { + if (am.mantissa) + { + int n = (std::numeric_limits::digits + - __builtin_clz (am.mantissa)) - 1; + am.mantissa &= ~(static_cast(1) << n); + am.mantissa <<= (binary_format::mantissa_explicit_bits() + - n); + am.power2 = n + 0x67; + } + } + else + { + am.mantissa <<= 13; + am.power2 += 0x70; + } + } + else + am.mantissa <<= 16; + to_float(pns.negative, am, *value.x); + return answer; + } +} // fast_float + } // anon namespace #endif @@ -490,11 +756,14 @@ namespace from_chars_result __floating_from_chars_hex(const char* first, const char* last, T& value) { - static_assert(is_same_v || is_same_v); - - using uint_t = conditional_t, uint32_t, uint64_t>; - constexpr int mantissa_bits = is_same_v ? 23 : 52; - constexpr int exponent_bits = is_same_v ? 8 : 11; + using uint_t = conditional_t, uint32_t, + conditional_t, uint64_t, + uint16_t>>; + constexpr int mantissa_bits + = fast_float::binary_format::mantissa_explicit_bits(); + constexpr int exponent_bits + = is_same_v ? 11 + : is_same_v ? 5 : 8; constexpr int exponent_bias = (1 << (exponent_bits - 1)) - 1; __glibcxx_requires_valid_range(first, last); @@ -520,12 +789,21 @@ namespace if (starts_with_ci(first, last, "inity"sv)) first += strlen("inity"); - uint_t result = 0; - result |= sign_bit; - result <<= exponent_bits; - result |= (1ull << exponent_bits) - 1; - result <<= mantissa_bits; - memcpy(&value, &result, sizeof(result)); + if constexpr (is_same_v || is_same_v) + { + uint_t result = 0; + result |= sign_bit; + result <<= exponent_bits; + result |= (1ull << exponent_bits) - 1; + result <<= mantissa_bits; + memcpy(&value, &result, sizeof(result)); + } + else + { + // float +/-Inf. + uint32_t result = 0x7F800000 | (sign_bit ? 0x80000000U : 0); + memcpy(value.x, &result, sizeof(result)); + } return {first, errc{}}; } @@ -566,12 +844,21 @@ namespace // We make the implementation-defined decision of ignoring the // sign bit and the n-char-sequence when assembling the NaN. - uint_t result = 0; - result <<= exponent_bits; - result |= (1ull << exponent_bits) - 1; - result <<= mantissa_bits; - result |= (1ull << (mantissa_bits - 1)) | 1; - memcpy(&value, &result, sizeof(result)); + if constexpr (is_same_v || is_same_v) + { + uint_t result = 0; + result <<= exponent_bits; + result |= (1ull << exponent_bits) - 1; + result <<= mantissa_bits; + result |= (1ull << (mantissa_bits - 1)) | 1; + memcpy(&value, &result, sizeof(result)); + } + else + { + // float qNaN. + uint32_t result = 0x7FC00001; + memcpy(value.x, &result, sizeof(result)); + } return {first, errc{}}; } @@ -633,18 +920,27 @@ namespace mantissa |= uint_t(hexit) << mantissa_idx; else if (mantissa_idx >= -4) { - if constexpr (is_same_v) + if constexpr (is_same_v + || is_same_v) { __glibcxx_assert(mantissa_idx == -1); mantissa |= hexit >> 1; midpoint_bit = (hexit & 0b0001) != 0; } - else + else if constexpr (is_same_v) { __glibcxx_assert(mantissa_idx == -4); midpoint_bit = (hexit & 0b1000) != 0; nonzero_tail = (hexit & 0b0111) != 0; } + else + { + __glibcxx_assert(mantissa_idx == -2); + mantissa |= hexit >> 2; + midpoint_bit = (hexit & 0b0010) != 0; + nonzero_tail = (hexit & 0b0001) != 0; + } } else nonzero_tail |= (hexit != 0x0); @@ -808,7 +1104,34 @@ namespace __glibcxx_assert(((mantissa & (1ull << mantissa_bits)) != 0) == (biased_exponent != 0)); } - memcpy(&value, &result, sizeof(result)); + if constexpr (is_same_v || is_same_v) + memcpy(&value, &result, sizeof(result)); + else if constexpr (is_same_v) + { + uint32_t res = uint32_t{result} << 16; + memcpy(value.x, &res, sizeof(res)); + } + else + { + // Otherwise float16_t which needs to be converted to float32_t. + uint32_t res; + if ((result & 0x7FFF) == 0) + res = uint32_t{result} << 16; // +/-0.0f16 + else if ((result & 0x7C00) == 0) + { // denormal + unsigned n = (std::numeric_limits::digits + - __builtin_clz (result & 0x3FF) - 1); + res = uint32_t{result} & 0x3FF & ~(uint32_t{1} << n); + res <<= 23 - n; + res |= (((uint32_t{n} + 0x67) << 23) + | ((uint32_t{result} & 0x8000) << 16)); + } + else + res = (((uint32_t{result} & 0x3FF) << 13) + | ((((uint32_t{result} >> 10) & 0x1F) + 0x70) << 23) + | ((uint32_t{result} & 0x8000) << 16)); + memcpy(value.x, &res, sizeof(res)); + } return {first, errc{}}; } @@ -826,9 +1149,7 @@ from_chars(const char* first, const char* last, float& value, if (fmt == chars_format::hex) return __floating_from_chars_hex(first, last, value); else - { - return fast_float::from_chars(first, last, value, fmt); - } + return fast_float::from_chars(first, last, value, fmt); #else return from_chars_strtod(first, last, value, fmt); #endif @@ -842,9 +1163,7 @@ from_chars(const char* first, const char* last, double& value, if (fmt == chars_format::hex) return __floating_from_chars_hex(first, last, value); else - { - return fast_float::from_chars(first, last, value, fmt); - } + return fast_float::from_chars(first, last, value, fmt); #else return from_chars_strtod(first, last, value, fmt); #endif @@ -863,9 +1182,7 @@ from_chars(const char* first, const char* last, long double& value, if (fmt == chars_format::hex) result = __floating_from_chars_hex(first, last, dbl_value); else - { - result = fast_float::from_chars(first, last, dbl_value, fmt); - } + result = fast_float::from_chars(first, last, dbl_value, fmt); if (result.ec == errc{}) value = dbl_value; return result; @@ -874,6 +1191,31 @@ from_chars(const char* first, const char* last, long double& value, #endif } +#if USE_LIB_FAST_FLOAT +// Entrypoints for 16-bit floats. +[[gnu::cold]] from_chars_result +__from_chars_float16_t(const char* first, const char* last, float& value, + chars_format fmt) noexcept +{ + struct fast_float::floating_type_float16_t val{ &value, 0 }; + if (fmt == chars_format::hex) + return __floating_from_chars_hex(first, last, val); + else + return fast_float::from_chars_16(first, last, val, fmt); +} + +[[gnu::cold]] from_chars_result +__from_chars_bfloat16_t(const char* first, const char* last, float& value, + chars_format fmt) noexcept +{ + struct fast_float::floating_type_bfloat16_t val{ &value, 0 }; + if (fmt == chars_format::hex) + return __floating_from_chars_hex(first, last, val); + else + return fast_float::from_chars_16(first, last, val, fmt); +} +#endif + #ifdef _GLIBCXX_LONG_DOUBLE_COMPAT // Make std::from_chars for 64-bit long double an alias for the overload // for double. diff --git a/libstdc++-v3/testsuite/20_util/to_chars/float16_c++23.cc b/libstdc++-v3/testsuite/20_util/to_chars/float16_c++23.cc new file mode 100644 index 00000000000..db935377c1c --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/to_chars/float16_c++23.cc @@ -0,0 +1,76 @@ +// Copyright (C) 2022 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// . + +// { dg-options "-std=gnu++2b" } +// { dg-do run { target c++23 } } +// { dg-require-effective-target ieee_floats } +// { dg-require-effective-target size32plus } +// { dg-add-options ieee } + +#include +#include +#include +#include +#include + +template +void +test(std::chars_format fmt = std::chars_format{}) +{ + char str1[128], str2[128], str3[128]; + union U { unsigned short s; T f; } u, v; + for (int i = 0; i <= (unsigned short) ~0; ++i) + { + u.s = i; + auto [ptr1, ec1] = std::to_chars(str1, str1 + sizeof(str1), u.f, fmt); + auto [ptr2, ec2] = std::to_chars(str2, str2 + sizeof(str2), std::float32_t(u.f), fmt); + VERIFY( ec1 == std::errc() && ec2 == std::errc()); +// std::cout << i << ' ' << std::string_view (str1, ptr1) +// << '\t' << std::string_view (str2, ptr2) << '\n'; + if (fmt == std::chars_format::fixed) + { + auto [ptr3, ec3] = std::to_chars(str3, str3 + (ptr1 - str1), u.f, fmt); + VERIFY( ec3 == std::errc() && ptr3 - str3 == ptr1 - str1 ); + auto [ptr4, ec4] = std::to_chars(str3, str3 + (ptr1 - str1 - 1), u.f, fmt); + VERIFY( ec4 != std::errc() ); + } + auto [ptr5, ec5] = std::from_chars(str1, ptr1, v.f, + fmt == std::chars_format{} + ? std::chars_format::general : fmt); + VERIFY( ec5 == std::errc() && ptr5 == ptr1 ); + VERIFY( u.s == v.s || (std::isnan(u.f) && std::isnan(v.f)) ); + } +} + +int +main() +{ +#ifdef __STDCPP_FLOAT16_T__ + test(); + test(std::chars_format::fixed); + test(std::chars_format::scientific); + test(std::chars_format::general); + test(std::chars_format::hex); +#endif +#ifdef __STDCPP_BFLOAT16_T__ + test(); + test(std::chars_format::fixed); + test(std::chars_format::scientific); + test(std::chars_format::general); + test(std::chars_format::hex); +#endif +}