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.129.124]) by sourceware.org (Postfix) with ESMTPS id CE0E3382DE21 for ; Thu, 27 Oct 2022 08:00:08 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org CE0E3382DE21 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=1666857608; 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=1PfRh3sxkDWW5l9d2vL6XUQkuOztaFBZBKvA8PxvRF8=; b=EfsThSOTW7Wqw+D9JydJUAjh06rvucu7YdeepeJ/OxMzaSZEg8tLs8P4RbKxwrV+HLJEc5 qyLm+jq2NBV1uRxCyuv2A7FGeokxDu0dMoTjxR4YKKCsp3BIJF2yGVaTZwwE9dtJMrbEN/ owWQgna+TMKtGlUC5NBsFaukMcitgSk= Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-131-XLmJrnblPV-igyRqcWnKuA-1; Thu, 27 Oct 2022 04:00:04 -0400 X-MC-Unique: XLmJrnblPV-igyRqcWnKuA-1 Received: from smtp.corp.redhat.com (int-mx08.intmail.prod.int.rdu2.redhat.com [10.11.54.8]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id CCC3687B2AE; Thu, 27 Oct 2022 08:00:03 +0000 (UTC) Received: from tucnak.zalov.cz (unknown [10.39.193.252]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 5F704C15BAB; Thu, 27 Oct 2022 08:00:03 +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 29R7xxAK271097 (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NOT); Thu, 27 Oct 2022 10:00:00 +0200 Received: (from jakub@localhost) by tucnak.zalov.cz (8.17.1/8.17.1/Submit) id 29R7xw0E271096; Thu, 27 Oct 2022 09:59:58 +0200 Date: Thu, 27 Oct 2022 09:59:58 +0200 From: Jakub Jelinek To: Jonathan Wakely , Patrick Palka Cc: gcc-patches@gcc.gnu.org, libstdc++@gcc.gnu.org Subject: [PATCH] libstdc++: std::to_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.8 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: multipart/mixed; boundary="Zt1l5+y2z6KOpavW" Content-Disposition: inline X-Spam-Status: No, score=-3.7 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=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: --Zt1l5+y2z6KOpavW Content-Type: text/plain; charset=us-ascii Content-Disposition: inline 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.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... 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: 00000000000ae824 950 FUNC GLOBAL DEFAULT 13 _ZSt21__to_chars_bfloat16_tPcS_fSt12chars_format@@GLIBCXX_3.4.31 5767: 00000000000ae4a1 899 FUNC GLOBAL DEFAULT 13 _ZSt20__to_chars_float16_tPcS_fSt12chars_format@@GLIBCXX_3.4.31 842: 000000000016d430 106 FUNC LOCAL DEFAULT 13 _ZN12_GLOBAL__N_113get_ieee_reprINS_23floating_type_float16_tEEENS_6ieee_tIT_EES3_ 865: 0000000000170980 1613 FUNC LOCAL DEFAULT 13 _ZSt23__floating_to_chars_hexIN12_GLOBAL__N_123floating_type_float16_tEESt15to_chars_resultPcS3_T_St8optionalIiE.constprop.0.isra.0 7205: 00000000000ae824 950 FUNC GLOBAL DEFAULT 13 _ZSt21__to_chars_bfloat16_tPcS_fSt12chars_format 7985: 00000000000ae4a1 899 FUNC GLOBAL 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 10000.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. (floating_type_traits, floating_type_traits, get_ieee_repr, get_ieee_repr, __handle_special_value, __handle_special_value): New specializations. (floating_to_shortest_scientific): Handle floating_type_float16_t and floating_type_bfloat16_t like IEEE quad. (__floating_to_chars_shortest): For floating_type_bfloat16_t call __floating_to_chars_hex rather than __floating_to_chars_hex to avoid instantiating the latter. (__to_chars_float16_t, __to_chars_bfloat16_t): New functions. --- libstdc++-v3/include/std/charconv.jj 2022-10-26 13:50:40.334716005 +0200 +++ libstdc++-v3/include/std/charconv 2022-10-26 14:19:46.523769686 +0200 @@ -738,6 +738,32 @@ namespace __detail to_chars_result to_chars(char* __first, char* __last, long double __value, chars_format __fmt, int __precision) noexcept; + // Library routines for 16-bit extended floating point formats + // using float as interchange format. + to_chars_result __to_chars_float16_t(char* __first, char* __last, + float __value, + chars_format __fmt) noexcept; + to_chars_result __to_chars_bfloat16_t(char* __first, char* __last, + float __value, + chars_format __fmt) noexcept; + +#if defined(__STDCPP_FLOAT16_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) + inline to_chars_result + to_chars(char* __first, char* __last, _Float16 __value) noexcept + { + return __to_chars_float16_t(__first, __last, float(__value), + chars_format{}); + } + inline to_chars_result + to_chars(char* __first, char* __last, _Float16 __value, + chars_format __fmt) noexcept + { return __to_chars_float16_t(__first, __last, float(__value), __fmt); } + inline to_chars_result + to_chars(char* __first, char* __last, _Float16 __value, + chars_format __fmt, int __precision) noexcept + { return to_chars(__first, __last, float(__value), __fmt, __precision); } +#endif + #if defined(__STDCPP_FLOAT32_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) inline to_chars_result to_chars(char* __first, char* __last, _Float32 __value) noexcept @@ -784,6 +810,24 @@ namespace __detail __precision); } #endif + +#if defined(__STDCPP_BFLOAT16_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) + inline to_chars_result + to_chars(char* __first, char* __last, + __gnu_cxx::__bfloat16_t __value) noexcept + { + return __to_chars_bfloat16_t(__first, __last, float(__value), + chars_format{}); + } + inline to_chars_result + to_chars(char* __first, char* __last, __gnu_cxx::__bfloat16_t __value, + chars_format __fmt) noexcept + { return __to_chars_bfloat16_t(__first, __last, float(__value), __fmt); } + inline to_chars_result + to_chars(char* __first, char* __last, __gnu_cxx::__bfloat16_t __value, + chars_format __fmt, int __precision) noexcept + { return to_chars(__first, __last, float(__value), __fmt, __precision); } +#endif #endif _GLIBCXX_END_NAMESPACE_VERSION --- libstdc++-v3/config/abi/pre/gnu.ver.jj 2022-09-12 11:30:14.211870202 +0200 +++ libstdc++-v3/config/abi/pre/gnu.ver 2022-10-26 16:11:53.146300799 +0200 @@ -2446,6 +2446,8 @@ GLIBCXX_3.4.30 { 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; } GLIBCXX_3.4.30; # Symbols in the support library (libsupc++) have their own tag. --- libstdc++-v3/src/c++17/floating_to_chars.cc.jj 2022-05-20 11:45:18.042741567 +0200 +++ libstdc++-v3/src/c++17/floating_to_chars.cc 2022-10-26 22:54:04.890144587 +0200 @@ -374,6 +374,44 @@ namespace }; #endif + // Wrappers around float for std::{,b}float16_t promoted to float. + struct floating_type_float16_t + { + float x; + operator float() const { return x; } + }; + struct floating_type_bfloat16_t + { + float x; + operator float() const { return x; } + }; + + template<> + struct floating_type_traits + { + static constexpr int mantissa_bits = 10; + static constexpr int exponent_bits = 5; + static constexpr bool has_implicit_leading_bit = true; + using mantissa_t = uint32_t; + using shortest_scientific_t = ryu::floating_decimal_128; + + static constexpr uint64_t pow10_adjustment_tab[] + = { 0 }; + }; + + template<> + struct floating_type_traits + { + static constexpr int mantissa_bits = 7; + static constexpr int exponent_bits = 8; + static constexpr bool has_implicit_leading_bit = true; + using mantissa_t = uint32_t; + using shortest_scientific_t = ryu::floating_decimal_128; + + static constexpr uint64_t pow10_adjustment_tab[] + = { 0b0000111001110001101010010110100101010010000000000000000000000000 }; + }; + // An IEEE-style decomposition of a floating-point value of type T. template struct ieee_t @@ -482,6 +520,79 @@ namespace } #endif + template<> + ieee_t + get_ieee_repr(const floating_type_float16_t value) + { + using mantissa_t = typename floating_type_traits::mantissa_t; + constexpr int mantissa_bits = floating_type_traits::mantissa_bits; + constexpr int exponent_bits = floating_type_traits::exponent_bits; + + uint32_t value_bits = 0; + memcpy(&value_bits, &value.x, sizeof(value)); + + ieee_t ieee_repr; + ieee_repr.mantissa + = static_cast(value_bits & ((uint32_t{1} << mantissa_bits) - 1u)); + value_bits >>= mantissa_bits; + ieee_repr.biased_exponent + = static_cast(value_bits & ((uint32_t{1} << exponent_bits) - 1u)); + value_bits >>= exponent_bits; + ieee_repr.sign = (value_bits & 1) != 0; + // We have mantissa and biased_exponent from the float (originally + // float16_t converted to float). + // Transform that to float16_t mantissa and biased_exponent. + // If biased_exponent is 0, then value is +-0.0. + // If biased_exponent is 0x67..0x70, then it is a float16_t denormal. + if (ieee_repr.biased_exponent >= 0x67 + && ieee_repr.biased_exponent <= 0x70) + { + int n = ieee_repr.biased_exponent - 0x67; + ieee_repr.mantissa = ((uint32_t{1} << n) + | (ieee_repr.mantissa >> (mantissa_bits - n))); + ieee_repr.biased_exponent = 0; + } + // If biased_exponent is 0xff, then it is a float16_t inf or NaN. + else if (ieee_repr.biased_exponent == 0xff) + { + ieee_repr.mantissa >>= 13; + ieee_repr.biased_exponent = 0x1f; + } + // If biased_exponent is 0x71..0x8e, then it is a float16_t normal number. + else if (ieee_repr.biased_exponent > 0x70) + { + ieee_repr.mantissa >>= 13; + ieee_repr.biased_exponent -= 0x70; + } + return ieee_repr; + } + + template<> + ieee_t + get_ieee_repr(const floating_type_bfloat16_t value) + { + using mantissa_t = typename floating_type_traits::mantissa_t; + constexpr int mantissa_bits = floating_type_traits::mantissa_bits; + constexpr int exponent_bits = floating_type_traits::exponent_bits; + + uint32_t value_bits = 0; + memcpy(&value_bits, &value.x, sizeof(value)); + + ieee_t ieee_repr; + ieee_repr.mantissa + = static_cast(value_bits & ((uint32_t{1} << mantissa_bits) - 1u)); + value_bits >>= mantissa_bits; + ieee_repr.biased_exponent + = static_cast(value_bits & ((uint32_t{1} << exponent_bits) - 1u)); + value_bits >>= exponent_bits; + ieee_repr.sign = (value_bits & 1) != 0; + // We have mantissa and biased_exponent from the float (originally + // bfloat16_t converted to float). + // Transform that to bfloat16_t mantissa and biased_exponent. + ieee_repr.mantissa >>= 16; + return ieee_repr; + } + // Invoke Ryu to obtain the shortest scientific form for the given // floating-point number. template @@ -493,7 +604,9 @@ namespace else if constexpr (std::is_same_v) return ryu::floating_to_fd64(value); else if constexpr (std::is_same_v - || std::is_same_v) + || std::is_same_v + || std::is_same_v + || std::is_same_v) { constexpr int mantissa_bits = floating_type_traits::mantissa_bits; @@ -678,6 +791,28 @@ template return {{first, errc{}}}; } +template<> + optional + __handle_special_value(char* first, + char* const last, + const floating_type_float16_t value, + const chars_format fmt, + const int precision) + { + return __handle_special_value(first, last, value.x, fmt, precision); + } + +template<> + optional + __handle_special_value(char* first, + char* const last, + const floating_type_bfloat16_t value, + const chars_format fmt, + const int precision) + { + return __handle_special_value(first, last, value.x, fmt, precision); + } + // This subroutine of the floating-point to_chars overloads performs // hexadecimal formatting. template @@ -922,7 +1057,15 @@ template chars_format fmt) { if (fmt == chars_format::hex) - return __floating_to_chars_hex(first, last, value, nullopt); + { + // 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) + return __floating_to_chars_hex(first, last, value.x, nullopt); + else + return __floating_to_chars_hex(first, last, value, nullopt); + } __glibcxx_assert(fmt == chars_format::fixed || fmt == chars_format::scientific @@ -1662,6 +1805,23 @@ to_chars(char* first, char* last, __floa } #endif +// Entrypoints for 16-bit floats. +[[gnu::cold]] to_chars_result +__to_chars_float16_t(char* first, char* last, float value, + chars_format fmt) noexcept +{ + return __floating_to_chars_shortest(first, last, + floating_type_float16_t{ value }, fmt); +} + +[[gnu::cold]] to_chars_result +__to_chars_bfloat16_t(char* first, char* last, float value, + chars_format fmt) noexcept +{ + return __floating_to_chars_shortest(first, last, + floating_type_bfloat16_t{ value }, fmt); +} + #ifdef _GLIBCXX_LONG_DOUBLE_COMPAT // Map the -mlong-double-64 long double overloads to the double overloads. extern "C" to_chars_result Jakub --Zt1l5+y2z6KOpavW Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="2.C" #include #include #include #include #include #include template void test(std::chars_format fmt = std::chars_format{}) { std::array str1, str2; 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.data(), str1.data() + str1.size(), u.f, fmt); auto [ptr2, ec2] = std::to_chars(str2.data(), str2.data() + str2.size(), std::float32_t(u.f), fmt); if (ec1 != std::errc()) { std::cout << std::make_error_code(ec1).message() << '\n'; continue; } else if (ec2 != std::errc()) { std::cout << std::make_error_code(ec2).message() << '\n'; continue; } std::cout << i << ' ' << std::string_view (str1.data(), ptr1) << '\t' << std::string_view (str2.data(), ptr2) << '\n'; if (fmt == std::chars_format::fixed) { auto [ptr3, ec3] = std::to_chars(str1.data(), ptr1, u.f, fmt); if (ec3 != std::errc() || ptr3 != ptr1) throw "Consistency failure"; else { auto [ptr4, ec4] = std::to_chars(str1.data(), ptr1 - 1, u.f, fmt); if (ec4 == std::errc()) throw "Consistency failure"; } } auto [ptr5, ec5] = std::to_chars(str1.data(), str1.data() + str1.size(), u.f, fmt); std::float32_t f; auto [ptr6, ec6] = std::from_chars(str1.data(), ptr5, f, fmt == std::chars_format{} ? std::chars_format::general : fmt); if (ec6 != std::errc()) { std::cout << std::make_error_code(ec6).message() << '\n'; continue; } v.f = T(f); if (u.s != v.s) { auto [ptr7, ec7] = std::to_chars(str2.data(), str2.data() + str1.size(), v.f, fmt); if (ec7 != std::errc()) { std::cout << std::make_error_code(ec7).message() << '\n'; continue; } std::cout << "Difference on " << i << ' ' << u.s << ' ' << v.s << ' ' << std::string_view (str1.data(), ptr5) << '\t' << std::string_view (str2.data(), ptr7) << '\n';; } } } int main() { #ifdef __STDCPP_FLOAT16_T__ std::cout << "float16_t" << '\n'; test(); std::cout << "float16_t fixed" << '\n'; test(std::chars_format::fixed); std::cout << "float16_t scientific" << '\n'; test(std::chars_format::scientific); std::cout << "float16_t general" << '\n'; test(std::chars_format::general); std::cout << "float16_t hex" << '\n'; test(std::chars_format::hex); #endif #ifdef __STDCPP_BFLOAT16_T__ std::cout << "bfloat16_t" << '\n'; test(); std::cout << "bfloat16_t fixed" << '\n'; test(std::chars_format::fixed); std::cout << "bfloat16_t scientific" << '\n'; test(std::chars_format::scientific); std::cout << "bfloat16_t general" << '\n'; test(std::chars_format::general); std::cout << "bfloat16_t hex" << '\n'; test(std::chars_format::hex); #endif } --Zt1l5+y2z6KOpavW Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="4.C" #include #include int main() { char buffer[1000]; #define CHECK_POW10(N) \ do { \ auto ec = std::to_chars(buffer, buffer+1000, 1e##N##bf16, std::chars_format::fixed); \ std::cout << (buffer[0] == '9' ? '1' : '0'); \ } while (0); CHECK_POW10(0); CHECK_POW10(1); CHECK_POW10(2); CHECK_POW10(3); CHECK_POW10(4); CHECK_POW10(5); CHECK_POW10(6); CHECK_POW10(7); CHECK_POW10(8); CHECK_POW10(9); CHECK_POW10(10); CHECK_POW10(11); CHECK_POW10(12); CHECK_POW10(13); CHECK_POW10(14); CHECK_POW10(15); CHECK_POW10(16); CHECK_POW10(17); CHECK_POW10(18); CHECK_POW10(19); CHECK_POW10(20); CHECK_POW10(21); CHECK_POW10(22); CHECK_POW10(23); CHECK_POW10(24); CHECK_POW10(25); CHECK_POW10(26); CHECK_POW10(27); CHECK_POW10(28); CHECK_POW10(29); CHECK_POW10(30); CHECK_POW10(31); CHECK_POW10(32); CHECK_POW10(33); CHECK_POW10(34); CHECK_POW10(35); CHECK_POW10(36); CHECK_POW10(37); CHECK_POW10(38); CHECK_POW10(39); CHECK_POW10(40); CHECK_POW10(41); CHECK_POW10(42); CHECK_POW10(43); CHECK_POW10(44); CHECK_POW10(45); CHECK_POW10(46); CHECK_POW10(47); CHECK_POW10(48); CHECK_POW10(49); CHECK_POW10(50); CHECK_POW10(51); CHECK_POW10(52); CHECK_POW10(53); CHECK_POW10(54); CHECK_POW10(55); CHECK_POW10(56); CHECK_POW10(57); CHECK_POW10(58); CHECK_POW10(59); CHECK_POW10(60); CHECK_POW10(61); CHECK_POW10(62); CHECK_POW10(63); std::cout << std::endl; } --Zt1l5+y2z6KOpavW--