From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id ACDF7385840E for ; Tue, 1 Nov 2022 09:36:48 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org ACDF7385840E Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1667295407; h=from:from:reply-to:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type; bh=La+D0dWsKc214MzUVQVB5wNVMFW9sdMBjc+znc7BrxM=; b=MTB5M4EzoCkhjsMfsgmfdZmZXRmfL1qFXLriJJksEuJLs45JmuHFua/8LEJClNvDQPZ2pJ nmZtDgo2jXuPwSm2hrPm8KB0woR6yTdope11ZF1GzGYFtG332LOoAbaQMeYgFq+DPxI3hX OymSOctR023UxMOoDkEUEX8Lgrpw00s= Received: from mimecast-mx02.redhat.com (mx3-rdu2.redhat.com [66.187.233.73]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-292-DPqAWKiAMyKNKx8jauyj4w-1; Tue, 01 Nov 2022 05:36:46 -0400 X-MC-Unique: DPqAWKiAMyKNKx8jauyj4w-1 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.rdu2.redhat.com [10.11.54.7]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id C058C29AB414; Tue, 1 Nov 2022 09:36:45 +0000 (UTC) Received: from tucnak.zalov.cz (unknown [10.39.193.252]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 3E78D140EBF3; Tue, 1 Nov 2022 09:36:45 +0000 (UTC) Received: from tucnak.zalov.cz (localhost [127.0.0.1]) by tucnak.zalov.cz (8.17.1/8.17.1) with ESMTPS id 2A19ag82011170 (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NOT); Tue, 1 Nov 2022 10:36:42 +0100 Received: (from jakub@localhost) by tucnak.zalov.cz (8.17.1/8.17.1/Submit) id 2A19afBJ011169; Tue, 1 Nov 2022 10:36:41 +0100 Date: Tue, 1 Nov 2022 10:36:40 +0100 From: Jakub Jelinek To: Jonathan Wakely , Patrick Palka Cc: gcc-patches@gcc.gnu.org, libstdc++@gcc.gnu.org Subject: [PATCH] libstdc++: std::from_chars std::{,b}float16_t support Message-ID: Reply-To: Jakub Jelinek MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.1 on 10.11.54.7 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset=us-ascii Content-Disposition: inline X-Spam-Status: No, score=-3.6 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,KAM_SHORT,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_NONE,TXREP autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: Hi! On top of the https://gcc.gnu.org/pipermail/libstdc++/2022-October/054849.html https://gcc.gnu.org/pipermail/libstdc++/2022-October/054886.html 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. Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk? 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. --- libstdc++-v3/include/std/charconv.jj 2022-10-28 11:15:40.113959052 +0200 +++ libstdc++-v3/include/std/charconv 2022-10-28 11:28:04.172657801 +0200 @@ -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, @@ -709,6 +735,22 @@ namespace __detail if (__res.ec == errc{}) __value = __val; 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 --- libstdc++-v3/config/abi/pre/gnu.ver.jj 2022-10-28 11:15:40.115959024 +0200 +++ libstdc++-v3/config/abi/pre/gnu.ver 2022-10-28 16:55:55.274849390 +0200 @@ -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. --- libstdc++-v3/src/c++17/floating_from_chars.cc.jj 2022-05-23 21:44:49.107846783 +0200 +++ libstdc++-v3/src/c++17/floating_from_chars.cc 2022-10-31 15:06:30.338480517 +0100 @@ -75,6 +75,272 @@ extern "C" __ieee128 __strtoieee128(cons 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 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 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 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 #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. --- libstdc++-v3/testsuite/20_util/to_chars/float16_c++23.cc.jj 2022-10-31 16:09:43.486130628 +0100 +++ libstdc++-v3/testsuite/20_util/to_chars/float16_c++23.cc 2022-10-31 16:27:00.690851973 +0100 @@ -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 +} Jakub