Message ID | Y2EOfVFPvNucL8ht@tucnak |
---|---|
State | New |
Headers | show |
Series | libstdc++: Shortest denormal hex std::to_chars | expand |
On Tue, 1 Nov 2022 at 12:18, Jakub Jelinek <jakub@redhat.com> wrote: > > On Fri, Oct 28, 2022 at 12:52:44PM -0400, Patrick Palka wrote: > > > 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.000000000000000000000000000000000235 > > > 2.35e-34 vs. 1.38p-112f32 > > > 0.00000000000000000000000000000000023472271 > > > 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. > > The following patch changes the behavior of the shortest hex denormals, > such that they are printed like normals (so for has_implicit_leading_bit > with 1p-149 instead of 0.000002p-126 etc., otherwise (Intel extended) > with the leading digit before dot being [89abcdef]). I think for all the > supported format it is never longer, it can be equal length e.g. for > 0.fffffep-126 vs. 1.fffffcp-127 but fortunately no largest subnormal > in any format has the unbiased exponent like -9, -99, -999, -9999 because > then it would be longer and often it is shorter, sometimes much shorter. > > For the cases with precision it keeps the handling as is. > > While for !has_implicit_leading_bit we for normals or with this patch > even denormals have really shortest representation, for other formats > we sometimes do not, but this patch doesn't deal with that (we > always use 1.NNN while we could use 1.NNN up to f.NNN and by that shortening > by the last hexit if the last hexit doesn't have least significant bit set > and unbiased exponent is not -9, -99, -999 or -9999. > > Tested on x86_64-linux (on top of the 3 to/from_chars {,b}float16_t > patches). This looks good to me. Please give Patrick a chance to comment, but it's approved for trunk unless he objects. Thanks! > > 2022-11-01 Jakub Jelinek <jakub@redhat.com> > > * src/c++17/floating_to_chars.cc (__floating_to_chars_hex): Drop const > from unbiased_exponent. Canonicalize denormals such that they have > the leading bit set by shifting effective mantissa up and decreasing > unbiased_exponent. > (__floating_to_chars_shortest): Don't instantiate > __floating_to_chars_hex for float16_t either and use float instead. > * testsuite/20_util/to_chars/float.cc (float_to_chars_test_cases): > Adjust testcases for shortest hex denormals. > * testsuite/20_util/to_chars/double.cc (double_to_chars_test_cases): > Likewise. > > --- libstdc++-v3/src/c++17/floating_to_chars.cc.jj 2022-10-31 22:20:35.881121902 +0100 > +++ libstdc++-v3/src/c++17/floating_to_chars.cc 2022-11-01 12:16:14.352652455 +0100 > @@ -844,9 +844,9 @@ template<typename T> > const bool is_normal_number = (biased_exponent != 0); > > // Calculate the unbiased exponent. > - const int32_t unbiased_exponent = (is_normal_number > - ? biased_exponent - exponent_bias > - : 1 - exponent_bias); > + int32_t unbiased_exponent = (is_normal_number > + ? biased_exponent - exponent_bias > + : 1 - exponent_bias); > > // Shift the mantissa so that its bitwidth is a multiple of 4. > constexpr unsigned rounded_mantissa_bits = (mantissa_bits + 3) / 4 * 4; > @@ -863,6 +863,16 @@ template<typename T> > __glibcxx_assert(effective_mantissa & (mantissa_t{1} << (mantissa_bits > - 1u))); > } > + else if (!precision.has_value() && effective_mantissa) > + { > + // 1.8p-23 is shorter than 0.00cp-14, so if precision is > + // omitted, try to canonicalize denormals such that they > + // have the leading bit set. > + int width = __bit_width(effective_mantissa); > + int shift = rounded_mantissa_bits - width + has_implicit_leading_bit; > + unbiased_exponent -= shift; > + effective_mantissa <<= shift; > + } > > // Compute the shortest precision needed to print this value exactly, > // disregarding trailing zeros. > @@ -1061,7 +1071,10 @@ template<typename T> > // std::bfloat16_t has the same exponent range as std::float32_t > // and so we can avoid instantiation of __floating_to_chars_hex > // for bfloat16_t. Shortest hex will be the same as for float. > - if constexpr (is_same_v<T, floating_type_bfloat16_t>) > + // When we print shortest form even for denormals, we can do it > + // for std::float16_t as well. > + if constexpr (is_same_v<T, floating_type_float16_t> > + || is_same_v<T, floating_type_bfloat16_t>) > return __floating_to_chars_hex(first, last, value.x, nullopt); > else > return __floating_to_chars_hex(first, last, value, nullopt); > --- libstdc++-v3/testsuite/20_util/to_chars/float.cc.jj 2022-01-11 22:31:41.605755528 +0100 > +++ libstdc++-v3/testsuite/20_util/to_chars/float.cc 2022-11-01 12:34:21.370882443 +0100 > @@ -521,8 +521,8 @@ inline constexpr float_to_chars_testcase > > // Test hexfloat corner cases. > {0x1.728p+0f, chars_format::hex, "1.728p+0"}, // instead of "2.e5p-1" > - {0x0.000002p-126f, chars_format::hex, "0.000002p-126"}, // instead of "1p-149", min subnormal > - {0x0.fffffep-126f, chars_format::hex, "0.fffffep-126"}, // max subnormal > + {0x0.000002p-126f, chars_format::hex, "1p-149"}, // min subnormal > + {0x0.fffffep-126f, chars_format::hex, "1.fffffcp-127"}, // max subnormal > {0x1p-126f, chars_format::hex, "1p-126"}, // min normal > {0x1.fffffep+127f, chars_format::hex, "1.fffffep+127"}, // max normal > > --- libstdc++-v3/testsuite/20_util/to_chars/double.cc.jj 2022-01-11 22:31:41.604755542 +0100 > +++ libstdc++-v3/testsuite/20_util/to_chars/double.cc 2022-11-01 12:42:39.753112522 +0100 > @@ -2821,8 +2821,8 @@ inline constexpr double_to_chars_testcas > > // Test hexfloat corner cases. > {0x1.728p+0, chars_format::hex, "1.728p+0"}, // instead of "2.e5p-1" > - {0x0.0000000000001p-1022, chars_format::hex, "0.0000000000001p-1022"}, // instead of "1p-1074", min subnormal > - {0x0.fffffffffffffp-1022, chars_format::hex, "0.fffffffffffffp-1022"}, // max subnormal > + {0x0.0000000000001p-1022, chars_format::hex, "1p-1074"}, // min subnormal > + {0x0.fffffffffffffp-1022, chars_format::hex, "1.ffffffffffffep-1023"}, // max subnormal > {0x1p-1022, chars_format::hex, "1p-1022"}, // min normal > {0x1.fffffffffffffp+1023, chars_format::hex, "1.fffffffffffffp+1023"}, // max normal > > > Jakub >
On Tue, 1 Nov 2022, Jonathan Wakely wrote: > On Tue, 1 Nov 2022 at 12:18, Jakub Jelinek <jakub@redhat.com> wrote: > > > > On Fri, Oct 28, 2022 at 12:52:44PM -0400, Patrick Palka wrote: > > > > 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.000000000000000000000000000000000235 > > > > 2.35e-34 vs. 1.38p-112f32 > > > > 0.00000000000000000000000000000000023472271 > > > > 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. > > > > The following patch changes the behavior of the shortest hex denormals, > > such that they are printed like normals (so for has_implicit_leading_bit > > with 1p-149 instead of 0.000002p-126 etc., otherwise (Intel extended) > > with the leading digit before dot being [89abcdef]). I think for all the > > supported format it is never longer, it can be equal length e.g. for > > 0.fffffep-126 vs. 1.fffffcp-127 but fortunately no largest subnormal > > in any format has the unbiased exponent like -9, -99, -999, -9999 because > > then it would be longer and often it is shorter, sometimes much shorter. > > > > For the cases with precision it keeps the handling as is. > > > > While for !has_implicit_leading_bit we for normals or with this patch > > even denormals have really shortest representation, for other formats > > we sometimes do not, but this patch doesn't deal with that (we > > always use 1.NNN while we could use 1.NNN up to f.NNN and by that shortening > > by the last hexit if the last hexit doesn't have least significant bit set > > and unbiased exponent is not -9, -99, -999 or -9999. > > > > Tested on x86_64-linux (on top of the 3 to/from_chars {,b}float16_t > > patches). > > This looks good to me. Please give Patrick a chance to comment, but > it's approved for trunk unless he objects. Thanks! LGTM. This'll mean the output of to_chars(denormal, hex, precision) will no longer be based on the shortest form to_chars(denormal, hex) which slightly bothers me, but doesn't seem to be nonconforming either. > > > > > > 2022-11-01 Jakub Jelinek <jakub@redhat.com> > > > > * src/c++17/floating_to_chars.cc (__floating_to_chars_hex): Drop const > > from unbiased_exponent. Canonicalize denormals such that they have > > the leading bit set by shifting effective mantissa up and decreasing > > unbiased_exponent. > > (__floating_to_chars_shortest): Don't instantiate > > __floating_to_chars_hex for float16_t either and use float instead. > > * testsuite/20_util/to_chars/float.cc (float_to_chars_test_cases): > > Adjust testcases for shortest hex denormals. > > * testsuite/20_util/to_chars/double.cc (double_to_chars_test_cases): > > Likewise. > > > > --- libstdc++-v3/src/c++17/floating_to_chars.cc.jj 2022-10-31 22:20:35.881121902 +0100 > > +++ libstdc++-v3/src/c++17/floating_to_chars.cc 2022-11-01 12:16:14.352652455 +0100 > > @@ -844,9 +844,9 @@ template<typename T> > > const bool is_normal_number = (biased_exponent != 0); > > > > // Calculate the unbiased exponent. > > - const int32_t unbiased_exponent = (is_normal_number > > - ? biased_exponent - exponent_bias > > - : 1 - exponent_bias); > > + int32_t unbiased_exponent = (is_normal_number > > + ? biased_exponent - exponent_bias > > + : 1 - exponent_bias); > > > > // Shift the mantissa so that its bitwidth is a multiple of 4. > > constexpr unsigned rounded_mantissa_bits = (mantissa_bits + 3) / 4 * 4; > > @@ -863,6 +863,16 @@ template<typename T> > > __glibcxx_assert(effective_mantissa & (mantissa_t{1} << (mantissa_bits > > - 1u))); > > } > > + else if (!precision.has_value() && effective_mantissa) > > + { > > + // 1.8p-23 is shorter than 0.00cp-14, so if precision is > > + // omitted, try to canonicalize denormals such that they > > + // have the leading bit set. > > + int width = __bit_width(effective_mantissa); > > + int shift = rounded_mantissa_bits - width + has_implicit_leading_bit; > > + unbiased_exponent -= shift; > > + effective_mantissa <<= shift; > > + } > > > > // Compute the shortest precision needed to print this value exactly, > > // disregarding trailing zeros. > > @@ -1061,7 +1071,10 @@ template<typename T> > > // std::bfloat16_t has the same exponent range as std::float32_t > > // and so we can avoid instantiation of __floating_to_chars_hex > > // for bfloat16_t. Shortest hex will be the same as for float. > > - if constexpr (is_same_v<T, floating_type_bfloat16_t>) > > + // When we print shortest form even for denormals, we can do it > > + // for std::float16_t as well. > > + if constexpr (is_same_v<T, floating_type_float16_t> > > + || is_same_v<T, floating_type_bfloat16_t>) > > return __floating_to_chars_hex(first, last, value.x, nullopt); > > else > > return __floating_to_chars_hex(first, last, value, nullopt); > > --- libstdc++-v3/testsuite/20_util/to_chars/float.cc.jj 2022-01-11 22:31:41.605755528 +0100 > > +++ libstdc++-v3/testsuite/20_util/to_chars/float.cc 2022-11-01 12:34:21.370882443 +0100 > > @@ -521,8 +521,8 @@ inline constexpr float_to_chars_testcase > > > > // Test hexfloat corner cases. > > {0x1.728p+0f, chars_format::hex, "1.728p+0"}, // instead of "2.e5p-1" > > - {0x0.000002p-126f, chars_format::hex, "0.000002p-126"}, // instead of "1p-149", min subnormal > > - {0x0.fffffep-126f, chars_format::hex, "0.fffffep-126"}, // max subnormal > > + {0x0.000002p-126f, chars_format::hex, "1p-149"}, // min subnormal > > + {0x0.fffffep-126f, chars_format::hex, "1.fffffcp-127"}, // max subnormal > > {0x1p-126f, chars_format::hex, "1p-126"}, // min normal > > {0x1.fffffep+127f, chars_format::hex, "1.fffffep+127"}, // max normal > > > > --- libstdc++-v3/testsuite/20_util/to_chars/double.cc.jj 2022-01-11 22:31:41.604755542 +0100 > > +++ libstdc++-v3/testsuite/20_util/to_chars/double.cc 2022-11-01 12:42:39.753112522 +0100 > > @@ -2821,8 +2821,8 @@ inline constexpr double_to_chars_testcas > > > > // Test hexfloat corner cases. > > {0x1.728p+0, chars_format::hex, "1.728p+0"}, // instead of "2.e5p-1" > > - {0x0.0000000000001p-1022, chars_format::hex, "0.0000000000001p-1022"}, // instead of "1p-1074", min subnormal > > - {0x0.fffffffffffffp-1022, chars_format::hex, "0.fffffffffffffp-1022"}, // max subnormal > > + {0x0.0000000000001p-1022, chars_format::hex, "1p-1074"}, // min subnormal > > + {0x0.fffffffffffffp-1022, chars_format::hex, "1.ffffffffffffep-1023"}, // max subnormal > > {0x1p-1022, chars_format::hex, "1p-1022"}, // min normal > > {0x1.fffffffffffffp+1023, chars_format::hex, "1.fffffffffffffp+1023"}, // max normal > > > > > > Jakub > > > >
--- libstdc++-v3/src/c++17/floating_to_chars.cc.jj 2022-10-31 22:20:35.881121902 +0100 +++ libstdc++-v3/src/c++17/floating_to_chars.cc 2022-11-01 12:16:14.352652455 +0100 @@ -844,9 +844,9 @@ template<typename T> const bool is_normal_number = (biased_exponent != 0); // Calculate the unbiased exponent. - const int32_t unbiased_exponent = (is_normal_number - ? biased_exponent - exponent_bias - : 1 - exponent_bias); + int32_t unbiased_exponent = (is_normal_number + ? biased_exponent - exponent_bias + : 1 - exponent_bias); // Shift the mantissa so that its bitwidth is a multiple of 4. constexpr unsigned rounded_mantissa_bits = (mantissa_bits + 3) / 4 * 4; @@ -863,6 +863,16 @@ template<typename T> __glibcxx_assert(effective_mantissa & (mantissa_t{1} << (mantissa_bits - 1u))); } + else if (!precision.has_value() && effective_mantissa) + { + // 1.8p-23 is shorter than 0.00cp-14, so if precision is + // omitted, try to canonicalize denormals such that they + // have the leading bit set. + int width = __bit_width(effective_mantissa); + int shift = rounded_mantissa_bits - width + has_implicit_leading_bit; + unbiased_exponent -= shift; + effective_mantissa <<= shift; + } // Compute the shortest precision needed to print this value exactly, // disregarding trailing zeros. @@ -1061,7 +1071,10 @@ template<typename T> // std::bfloat16_t has the same exponent range as std::float32_t // and so we can avoid instantiation of __floating_to_chars_hex // for bfloat16_t. Shortest hex will be the same as for float. - if constexpr (is_same_v<T, floating_type_bfloat16_t>) + // When we print shortest form even for denormals, we can do it + // for std::float16_t as well. + if constexpr (is_same_v<T, floating_type_float16_t> + || is_same_v<T, floating_type_bfloat16_t>) return __floating_to_chars_hex(first, last, value.x, nullopt); else return __floating_to_chars_hex(first, last, value, nullopt); --- libstdc++-v3/testsuite/20_util/to_chars/float.cc.jj 2022-01-11 22:31:41.605755528 +0100 +++ libstdc++-v3/testsuite/20_util/to_chars/float.cc 2022-11-01 12:34:21.370882443 +0100 @@ -521,8 +521,8 @@ inline constexpr float_to_chars_testcase // Test hexfloat corner cases. {0x1.728p+0f, chars_format::hex, "1.728p+0"}, // instead of "2.e5p-1" - {0x0.000002p-126f, chars_format::hex, "0.000002p-126"}, // instead of "1p-149", min subnormal - {0x0.fffffep-126f, chars_format::hex, "0.fffffep-126"}, // max subnormal + {0x0.000002p-126f, chars_format::hex, "1p-149"}, // min subnormal + {0x0.fffffep-126f, chars_format::hex, "1.fffffcp-127"}, // max subnormal {0x1p-126f, chars_format::hex, "1p-126"}, // min normal {0x1.fffffep+127f, chars_format::hex, "1.fffffep+127"}, // max normal --- libstdc++-v3/testsuite/20_util/to_chars/double.cc.jj 2022-01-11 22:31:41.604755542 +0100 +++ libstdc++-v3/testsuite/20_util/to_chars/double.cc 2022-11-01 12:42:39.753112522 +0100 @@ -2821,8 +2821,8 @@ inline constexpr double_to_chars_testcas // Test hexfloat corner cases. {0x1.728p+0, chars_format::hex, "1.728p+0"}, // instead of "2.e5p-1" - {0x0.0000000000001p-1022, chars_format::hex, "0.0000000000001p-1022"}, // instead of "1p-1074", min subnormal - {0x0.fffffffffffffp-1022, chars_format::hex, "0.fffffffffffffp-1022"}, // max subnormal + {0x0.0000000000001p-1022, chars_format::hex, "1p-1074"}, // min subnormal + {0x0.fffffffffffffp-1022, chars_format::hex, "1.ffffffffffffep-1023"}, // max subnormal {0x1p-1022, chars_format::hex, "1p-1022"}, // min normal {0x1.fffffffffffffp+1023, chars_format::hex, "1.fffffffffffffp+1023"}, // max normal