[PATCH] libstdc++: std::to_chars std::{,b}float16_t support
Hi! The following patch on top of https://gcc.gnu.org/pipermail/libstdc++/2022-October/054849.html adds std::{,b}float16_t support for std::to_chars. When precision is specified (or for std::bfloat16_t for hex mode even if not), I believe we can just use the std::to_chars float (when float is mode compatible with std::float32_t) overloads, both formats are proper subsets of std::float32_t. Unfortunately when precision is not specified and we are supposed to emit shortest string, the std::{,b}float16_t strings are usually much shorter. E.g. 1.e7p-14f16 shortest fixed representation is 0.0001161 and shortest scientific representation is 1.161e-04 while 1.e7p-14f32 (same number promoted to std::float32_t) 0.00011610985 and 1.1610985e-04. Similarly for 1.38p-112bf16, 0.0235 2.35e-34 vs. 1.38p-112f32 0.023472271 2.3472271e-34 For std::float16_t there are differences even in the shortest hex, say: 0.01p-14 vs. 1p-22 but only for denormal std::float16_t values (where all std::float16_t denormals converted to std::float32_t are normal), __FLT16_MIN__ and everything larger in absolute value than that is the same. Unless that is a bug and we should try to discover shorter representations even for denormals... std::bfloat16_t has the same exponent range as std::float32_t, so all std::bfloat16_t denormals are also std::float32_t denormals and thus the shortest hex representations are the same. As documented, ryu can handle arbitrary IEEE like floating point formats (probably not wider than IEEE quad) using the generic_128 handling, but ryu is hidden in libstdc++.so. As only few architectures support std::float16_t right now and some of them have special ISA requirements for those (e.g. on i?86 one needs -msse2) and std::bfloat16_t is right now supported only on x86 (again with -msse2), perhaps with aarch64/arm coming next if ARM is interested, but I think it is possible that more will be added later, instead of exporting APIs from the library to handle directly the std::{,b}float16_t overloads this patch instead exports functions which take a float which is a superset of those and expects the inline overloads to promote the 16-bit formats to 32-bit, then inside of the library it ensures they are printed right. With the added [[gnu::cold]] attribute because I think most users will primarily use these formats as storage formats and perform arithmetics in the excess precision for them and print also as std::float32_t the added support doesn't seem to be too large, on x86_64: readelf -Ws libstdc++.so.6.0.31 | grep float16_t 912: 000ae824 950 FUNCGLOBAL DEFAULT 13 _ZSt21__to_chars_bfloat16_tPcS_fSt12chars_format@@GLIBCXX_3.4.31 5767: 000ae4a1 899 FUNCGLOBAL DEFAULT 13 _ZSt20__to_chars_float16_tPcS_fSt12chars_format@@GLIBCXX_3.4.31 842: 0016d430 106 FUNCLOCAL DEFAULT 13 _ZN12_GLOBAL__N_113get_ieee_reprINS_23floating_type_float16_tEEENS_6ieee_tIT_EES3_ 865: 00170980 1613 FUNCLOCAL DEFAULT 13 _ZSt23__floating_to_chars_hexIN12_GLOBAL__N_123floating_type_float16_tEESt15to_chars_resultPcS3_T_St8optionalIiE.constprop.0.isra.0 7205: 000ae824 950 FUNCGLOBAL DEFAULT 13 _ZSt21__to_chars_bfloat16_tPcS_fSt12chars_format 7985: 000ae4a1 899 FUNCGLOBAL DEFAULT 13 _ZSt20__to_chars_float16_tPcS_fSt12chars_format so 3568 code bytes together or so. Tested with the attached test (which doesn't prove the shortest representation, just prints std::{,b}float16_t and std::float32_t shortest strings side by side, then tries to verify it can be emitted even into the exact sized range and can't be into range one smaller than that and tries to read what is printed back using from_chars float32_t overload (so there could be double rounding, but apparently there is none for the shortest strings). The only differences printed are for NaNs, where sNaNs are canonicalized to canonical qNaNs and as to_chars doesn't print NaN mantissa, even qNaNs other than the canonical one are read back just as the canonical NaN. Also attaching what Patrick wrote to generate the pow10_adjustment_tab, for std::float16_t only 1.0, 10.0, 100.0, 1000.0 and 1.0 are powers of 10 in the range because __FLT16_MAX__ is 65504.0, and all of the above are exactly representable in std::float16_t, so we want to use 0 in pow10_adjustment_tab. Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk? 2022-10-27 Jakub Jelinek * include/std/charconv (__to_chars_float16_t, __to_chars_bfloat16_t): Declare. (to_chars): Add _Float16 and __gnu_cxx::__bfloat16_t overloads. * config/abi/pre/gnu.ver (GLIBCXX_3.4.31): Export _ZSt20__to_chars_float16_tPcS_fSt12chars_format and _ZSt21__to_chars_bfloat16_tPcS_fSt12chars_format. * src/c++17/floating_to_chars.cc (floating_type_float16_t, floating_type_bfloat16_t): New types. (floa
Re: [PATCH] libstdc++: std::to_chars std::{,b}float16_t support
On Thu, 27 Oct 2022, Jakub Jelinek wrote: > Hi! > > The following patch on top of > https://gcc.gnu.org/pipermail/libstdc++/2022-October/054849.html > adds std::{,b}float16_t support for std::to_chars. > When precision is specified (or for std::bfloat16_t for hex mode even if not), > I believe we can just use the std::to_chars float (when float is mode > compatible with std::float32_t) overloads, both formats are proper subsets > of std::float32_t. > Unfortunately when precision is not specified and we are supposed to emit > shortest string, the std::{,b}float16_t strings are usually much shorter. > E.g. 1.e7p-14f16 shortest fixed representation is > 0.0001161 and shortest scientific representation is > 1.161e-04 while 1.e7p-14f32 (same number promoted to std::float32_t) > 0.00011610985 and > 1.1610985e-04. > Similarly for 1.38p-112bf16, > 0.0235 > 2.35e-34 vs. 1.38p-112f32 > 0.023472271 > 2.3472271e-34 > For std::float16_t there are differences even in the shortest hex, say: > 0.01p-14 vs. 1p-22 > but only for denormal std::float16_t values (where all std::float16_t > denormals converted to std::float32_t are normal), __FLT16_MIN__ and > everything larger in absolute value than that is the same. Unless > that is a bug and we should try to discover shorter representations > even for denormals... IIRC for hex formatting of denormals I opted to be consistent with how glibc printf formats them, instead of outputting the truly shortest form. I wouldn't be against using the float32 overloads even for shortest hex formatting of float16. The output is shorter but equivalent so it shouldn't cause any problems. > std::bfloat16_t has the same exponent range as std::float32_t, so all > std::bfloat16_t denormals are also std::float32_t denormals and thus > the shortest hex representations are the same. > > As documented, ryu can handle arbitrary IEEE like floating point formats > (probably not wider than IEEE quad) using the generic_128 handling, but > ryu is hidden in libstdc++.so. As only few architectures support > std::float16_t right now and some of them have special ISA requirements > for those (e.g. on i?86 one needs -msse2) and std::bfloat16_t is right > now supported only on x86 (again with -msse2), perhaps with aarch64/arm > coming next if ARM is interested, but I think it is possible that more > will be added later, instead of exporting APIs from the library to handle > directly the std::{,b}float16_t overloads this patch instead exports > functions which take a float which is a superset of those and expects > the inline overloads to promote the 16-bit formats to 32-bit, then inside > of the library it ensures they are printed right. > With the added [[gnu::cold]] attribute because I think most users > will primarily use these formats as storage formats and perform arithmetics > in the excess precision for them and print also as std::float32_t the > added support doesn't seem to be too large, on x86_64: > readelf -Ws libstdc++.so.6.0.31 | grep float16_t >912: 000ae824 950 FUNCGLOBAL DEFAULT 13 > _ZSt21__to_chars_bfloat16_tPcS_fSt12chars_format@@GLIBCXX_3.4.31 > 5767: 000ae4a1 899 FUNCGLOBAL DEFAULT 13 > _ZSt20__to_chars_float16_tPcS_fSt12chars_format@@GLIBCXX_3.4.31 >842: 0016d430 106 FUNCLOCAL DEFAULT 13 > _ZN12_GLOBAL__N_113get_ieee_reprINS_23floating_type_float16_tEEENS_6ieee_tIT_EES3_ >865: 00170980 1613 FUNCLOCAL DEFAULT 13 > _ZSt23__floating_to_chars_hexIN12_GLOBAL__N_123floating_type_float16_tEESt15to_chars_resultPcS3_T_St8optionalIiE.constprop.0.isra.0 > 7205: 000ae824 950 FUNCGLOBAL DEFAULT 13 > _ZSt21__to_chars_bfloat16_tPcS_fSt12chars_format > 7985: 000ae4a1 899 FUNCGLOBAL DEFAULT 13 > _ZSt20__to_chars_float16_tPcS_fSt12chars_format > so 3568 code bytes together or so. Ouch, the instantiation of __floating_to_chars_hex for float16 is responsible for nearly 50% of the .so size increase > > Tested with the attached test (which doesn't prove the shortest > representation, just prints std::{,b}float16_t and std::float32_t > shortest strings side by side, then tries to verify it can be > emitted even into the exact sized range and can't be into range > one smaller than that and tries to read what is printed > back using from_chars float32_t overload (so there could be > double rounding, but apparently there is none for the shortest strings). > The only differences printed are for NaNs, where sNaNs are canonicalized > to canonical qNaNs and as to_chars doesn't print NaN mantissa, even qNaNs > other than the canonical one are read back just as the canonical NaN. > > Also attaching what Patrick wrote to generate the pow10_adjustment_tab, > for std::float16_t only 1.0, 10.0, 100.0, 1000.0 and 1.0 are powers > of 10 in the range because __FLT16_MAX__ is 65504.0, and all of the above > are exactly representab
Re: [PATCH] libstdc++: std::to_chars std::{,b}float16_t support
On Fri, Oct 28, 2022 at 12:52:44PM -0400, Patrick Palka wrote: > IIRC for hex formatting of denormals I opted to be consistent with how > glibc printf formats them, instead of outputting the truly shortest > form. Note, it isn't just denormals, 1.18cp-4 2.318p-5 4.63p-6 8.c6p-7 463p-10 8c6p-11 also represent the same number, the first is what glibc emits (and is certainly nicer to read), but some of the others are shorter. Now, the printf %a/%A documentation says that there must be one hexadecimal digit before the dot if any and that for normalized numbers it must be non-zero. So that rules out the last 2, and allows but doesn't require the denormal treatment the library does right now. If we shall go really for the shortest, we should handle denormals with non-zero leading digit too and for all cases consider the 4 shifting possibilities which one results in shortest (perhaps prefer the smallest non-zero leading digit among the shortest)? > > readelf -Ws libstdc++.so.6.0.31 | grep float16_t > >912: 000ae824 950 FUNCGLOBAL DEFAULT 13 > > _ZSt21__to_chars_bfloat16_tPcS_fSt12chars_format@@GLIBCXX_3.4.31 > > 5767: 000ae4a1 899 FUNCGLOBAL DEFAULT 13 > > _ZSt20__to_chars_float16_tPcS_fSt12chars_format@@GLIBCXX_3.4.31 > >842: 0016d430 106 FUNCLOCAL DEFAULT 13 > > _ZN12_GLOBAL__N_113get_ieee_reprINS_23floating_type_float16_tEEENS_6ieee_tIT_EES3_ > >865: 00170980 1613 FUNCLOCAL DEFAULT 13 > > _ZSt23__floating_to_chars_hexIN12_GLOBAL__N_123floating_type_float16_tEESt15to_chars_resultPcS3_T_St8optionalIiE.constprop.0.isra.0 > > 7205: 000ae824 950 FUNCGLOBAL DEFAULT 13 > > _ZSt21__to_chars_bfloat16_tPcS_fSt12chars_format > > 7985: 000ae4a1 899 FUNCGLOBAL DEFAULT 13 > > _ZSt20__to_chars_float16_tPcS_fSt12chars_format > > so 3568 code bytes together or so. > > Ouch, the instantiation of __floating_to_chars_hex for float16 is > responsible for nearly 50% of the .so size increase True, but the increase isn't that huge. Jakub
Re: [PATCH] libstdc++: std::to_chars std::{,b}float16_t support
On Thu, 27 Oct 2022 at 09:00, Jakub Jelinek wrote: > > Hi! > > The following patch on top of > https://gcc.gnu.org/pipermail/libstdc++/2022-October/054849.html > adds std::{,b}float16_t support for std::to_chars. > When precision is specified (or for std::bfloat16_t for hex mode even if not), > I believe we can just use the std::to_chars float (when float is mode > compatible with std::float32_t) overloads, both formats are proper subsets > of std::float32_t. > Unfortunately when precision is not specified and we are supposed to emit > shortest string, the std::{,b}float16_t strings are usually much shorter. > E.g. 1.e7p-14f16 shortest fixed representation is > 0.0001161 and shortest scientific representation is > 1.161e-04 while 1.e7p-14f32 (same number promoted to std::float32_t) > 0.00011610985 and > 1.1610985e-04. > Similarly for 1.38p-112bf16, > 0.0235 > 2.35e-34 vs. 1.38p-112f32 > 0.023472271 > 2.3472271e-34 > For std::float16_t there are differences even in the shortest hex, say: > 0.01p-14 vs. 1p-22 > but only for denormal std::float16_t values (where all std::float16_t > denormals converted to std::float32_t are normal), __FLT16_MIN__ and > everything larger in absolute value than that is the same. Unless > that is a bug and we should try to discover shorter representations > even for denormals... > std::bfloat16_t has the same exponent range as std::float32_t, so all > std::bfloat16_t denormals are also std::float32_t denormals and thus > the shortest hex representations are the same. > > As documented, ryu can handle arbitrary IEEE like floating point formats > (probably not wider than IEEE quad) using the generic_128 handling, but > ryu is hidden in libstdc++.so. As only few architectures support > std::float16_t right now and some of them have special ISA requirements > for those (e.g. on i?86 one needs -msse2) and std::bfloat16_t is right > now supported only on x86 (again with -msse2), perhaps with aarch64/arm > coming next if ARM is interested, but I think it is possible that more > will be added later, instead of exporting APIs from the library to handle > directly the std::{,b}float16_t overloads this patch instead exports > functions which take a float which is a superset of those and expects > the inline overloads to promote the 16-bit formats to 32-bit, then inside > of the library it ensures they are printed right. > With the added [[gnu::cold]] attribute because I think most users > will primarily use these formats as storage formats and perform arithmetics > in the excess precision for them and print also as std::float32_t the > added support doesn't seem to be too large, on x86_64: > readelf -Ws libstdc++.so.6.0.31 | grep float16_t >912: 000ae824 950 FUNCGLOBAL DEFAULT 13 > _ZSt21__to_chars_bfloat16_tPcS_fSt12chars_format@@GLIBCXX_3.4.31 > 5767: 000ae4a1 899 FUNCGLOBAL DEFAULT 13 > _ZSt20__to_chars_float16_tPcS_fSt12chars_format@@GLIBCXX_3.4.31 >842: 0016d430 106 FUNCLOCAL DEFAULT 13 > _ZN12_GLOBAL__N_113get_ieee_reprINS_23floating_type_float16_tEEENS_6ieee_tIT_EES3_ >865: 00170980 1613 FUNCLOCAL DEFAULT 13 > _ZSt23__floating_to_chars_hexIN12_GLOBAL__N_123floating_type_float16_tEESt15to_chars_resultPcS3_T_St8optionalIiE.constprop.0.isra.0 > 7205: 000ae824 950 FUNCGLOBAL DEFAULT 13 > _ZSt21__to_chars_bfloat16_tPcS_fSt12chars_format > 7985: 000ae4a1 899 FUNCGLOBAL DEFAULT 13 > _ZSt20__to_chars_float16_tPcS_fSt12chars_format > so 3568 code bytes together or so. > > Tested with the attached test (which doesn't prove the shortest > representation, just prints std::{,b}float16_t and std::float32_t > shortest strings side by side, then tries to verify it can be > emitted even into the exact sized range and can't be into range > one smaller than that and tries to read what is printed > back using from_chars float32_t overload (so there could be > double rounding, but apparently there is none for the shortest strings). > The only differences printed are for NaNs, where sNaNs are canonicalized > to canonical qNaNs and as to_chars doesn't print NaN mantissa, even qNaNs > other than the canonical one are read back just as the canonical NaN. > > Also attaching what Patrick wrote to generate the pow10_adjustment_tab, > for std::float16_t only 1.0, 10.0, 100.0, 1000.0 and 1.0 are powers > of 10 in the range because __FLT16_MAX__ is 65504.0, and all of the above > are exactly representable in std::float16_t, so we want to use 0 in > pow10_adjustment_tab. > > Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk? > Unless I misunderstood something in Patrick's review, this is good and can be incrementally improved. OK for trunk, thanks.