Skip to content

Commit

Permalink
Merge pull request #60 from mborland/from_chars_ld
Browse files Browse the repository at this point in the history
128-bit from_chars
  • Loading branch information
mborland authored Aug 4, 2023
2 parents c92eb9e + 371e443 commit 43042ed
Show file tree
Hide file tree
Showing 18 changed files with 1,075 additions and 241 deletions.
90 changes: 90 additions & 0 deletions benchmark/from_chars_floating.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2023 Peter Dimov
// Copyright 2023 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

Expand All @@ -13,6 +14,7 @@
#include <iostream>
#include <iomanip>
#include <charconv>
#include <random>

constexpr unsigned N = 2'000'000;
constexpr int K = 10;
Expand Down Expand Up @@ -40,6 +42,27 @@ template<class T> static BOOST_NOINLINE void init_input_data( std::vector<std::s
}
}

template <>
BOOST_NOINLINE void init_input_data<long double>( std::vector<std::string>& data, bool general )
{
data.reserve(N / 10);

std::random_device rd;
std::mt19937_64 rng(rd());
std::uniform_real_distribution<long double> dist(0.0L, (std::numeric_limits<long double>::max)());

for( unsigned i = 0; i < N / 10; ++i )
{
const long double x = dist(rng);

char buffer[ 64 ];
auto r = boost::charconv::to_chars( buffer, buffer + sizeof( buffer ), x, general? boost::charconv::chars_format::general : boost::charconv::chars_format::scientific );

std::string y( buffer, r.ptr );
data.push_back( y );
}
}

template<class T> static BOOST_NOINLINE void init_input_data_uint64( std::vector<std::string>& data )
{
data.reserve( N );
Expand Down Expand Up @@ -102,6 +125,26 @@ template<> BOOST_NOINLINE void test_strtox<double>( std::vector<std::string> con
std::cout << " std::strtox<double>, " << label << ": " << std::setw( 5 ) << ( t2 - t1 ) / 1ms << " ms (s=" << s << ")\n";
}

template<> BOOST_NOINLINE void test_strtox<long double>( std::vector<std::string> const& data, bool, char const* label )
{
auto t1 = std::chrono::steady_clock::now();

long double s = 0;

for( int i = 0; i < K; ++i )
{
for( auto const& x: data )
{
double y = std::strtold( x.c_str(), nullptr );
s = s / 16.0L + y;
}
}

auto t2 = std::chrono::steady_clock::now();

std::cout << " std::strtox<long double>, " << label << ": " << std::setw( 5 ) << ( t2 - t1 ) / 1ms << " ms (s=" << s << ")\n";
}

template<class T> static BOOST_NOINLINE void test_std_from_chars( std::vector<std::string> const& data, bool general, char const* label )
{
auto t1 = std::chrono::steady_clock::now();
Expand All @@ -124,6 +167,28 @@ template<class T> static BOOST_NOINLINE void test_std_from_chars( std::vector<st
std::cout << " std::from_chars<" << boost::core::type_name<T>() << ">, " << label << ": " << std::setw( 5 ) << ( t2 - t1 ) / 1ms << " ms (s=" << s << ")\n";
}

template<> BOOST_NOINLINE void test_std_from_chars<long double>( std::vector<std::string> const& data, bool general, char const* label )
{
auto t1 = std::chrono::steady_clock::now();

long double s = 0;

for( int i = 0; i < K; ++i )
{
for( auto const& x: data )
{
long double y;
std::from_chars( x.data(), x.data() + x.size(), y, general? std::chars_format::general: std::chars_format::scientific );

s = s / 16.0L + y;
}
}

auto t2 = std::chrono::steady_clock::now();

std::cout << " std::from_chars<long double>, " << label << ": " << std::setw( 5 ) << ( t2 - t1 ) / 1ms << " ms (s=" << s << ")\n";
}

template<class T> static BOOST_NOINLINE void test_boost_from_chars( std::vector<std::string> const& data, bool general, char const* label )
{
auto t1 = std::chrono::steady_clock::now();
Expand All @@ -146,6 +211,29 @@ template<class T> static BOOST_NOINLINE void test_boost_from_chars( std::vector<
std::cout << "boost::charconv::from_chars<" << boost::core::type_name<T>() << ">, " << label << ": " << std::setw( 5 ) << ( t2 - t1 ) / 1ms << " ms (s=" << s << ")\n";
}

template<>
BOOST_NOINLINE void test_boost_from_chars<long double>( std::vector<std::string> const& data, bool general, char const* label )
{
auto t1 = std::chrono::steady_clock::now();

long double s = 0;

for( int i = 0; i < K; ++i )
{
for( auto const& x: data )
{
long double y;
boost::charconv::from_chars( x.data(), x.data() + x.size(), y, general? boost::charconv::chars_format::general: boost::charconv::chars_format::scientific );

s = s / 16.0L + y;
}
}

auto t2 = std::chrono::steady_clock::now();

std::cout << "boost::charconv::from_chars<long double>, " << label << ": " << std::setw( 5 ) << ( t2 - t1 ) / 1ms << " ms (s=" << s << ")\n";
}

template<class T> static void test( bool general )
{
std::vector<std::string> data;
Expand Down Expand Up @@ -182,9 +270,11 @@ int main()

test<float>( false );
test<double>( false );
test<long double>( false );

test<float>( true );
test<double>( true );
test<long double>( true );

test2<float>();
test2<double>();
Expand Down
3 changes: 3 additions & 0 deletions doc/charconv/from_chars.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ from_chars_result from_chars(const char* first, const char* last, Real& value, c
=== from_chars for floating point types
* On std::errc::result_out_of_range we return ±0 for small values (e.g. 1.0e-99999) or ±HUGE_VAL for large values (e.g. 1.0e+99999) to match the handling of `std::strtod`.
This is a divergence from the standard which states we should return the `value` argument unmodified.
* These functions have been tested to support all built-in floating-point types and those from C++23's `<stdfloat>`
** Long doubles can be either 64, 80, or 128-bit but must be IEEE 754 compliant. An example of a non-compliant, and therefore unsupported format is `ibm128`.
** Use of `__float128` or `std::float128_t` requires compiling with `-std=gnu++xx` and linking GCC's `libquadmath`.

== Examples

Expand Down
54 changes: 54 additions & 0 deletions include/boost/charconv/detail/bit.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2023 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#ifndef BOOST_CHARCONV_DETAIL_BIT_HPP
#define BOOST_CHARCONV_DETAIL_BIT_HPP

#include <boost/charconv/detail/config.hpp>
#include <boost/charconv/detail/emulated128.hpp>
#include <boost/core/bit.hpp>
#include <type_traits>
#include <limits>
#include <cstdint>

// Derivative of the following SO answer:
// https://stackoverflow.com/questions/28423405/counting-the-number-of-leading-zeros-in-a-128-bit-integer

namespace boost { namespace charconv { namespace detail {

template <typename T>
inline int clz_u128(T val) noexcept
{
static_assert(sizeof(T) == 16 && (!std::numeric_limits<T>::is_signed
#ifdef BOOST_CHARCONV_HAS_INT128
// May not have numeric_limits specialization without gnu mode
|| std::is_same<T, boost::uint128_type>::value
#endif
), "This function is only for 128-bit unsigned types");

const std::uint64_t hi = val >> 64;
const std::uint64_t lo = val;

if (hi != 0)
{
return boost::core::countl_zero(hi);
}

return boost::core::countl_zero(lo) + 64;
}

template <>
inline int clz_u128<uint128>(uint128 val) noexcept
{
if (val.high != 0)
{
return boost::core::countl_zero(val.high);
}

return boost::core::countl_zero(val.low) + 64;
}

}}} // Namespaces

#endif // BOOST_CHARCONV_DETAIL_BIT_HPP
151 changes: 134 additions & 17 deletions include/boost/charconv/detail/compute_float80.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,158 @@
#define BOOST_CHARCONV_DETAIL_COMPUTE_FLOAT80_HPP

#include <boost/charconv/detail/config.hpp>
#include <boost/charconv/detail/emulated128.hpp>
#include <boost/charconv/detail/bit_layouts.hpp>
#include <system_error>
#include <type_traits>
#include <limits>
#include <cstdint>
#include <cmath>
#include <climits>
#include <cfloat>

#ifdef BOOST_CHARCONV_DEBUG_FLOAT128
#include <iostream>
#include <iomanip>
#include <boost/charconv/detail/to_chars_integer_impl.hpp>
#endif

namespace boost { namespace charconv { namespace detail {

inline long double compute_float80(std::int64_t power, std::uint64_t i, bool negative, bool& success) noexcept
#if BOOST_CHARCONV_LDBL_BITS > 64

static constexpr long double powers_of_ten_ld[] = {
1e0L, 1e1L, 1e2L, 1e3L, 1e4L, 1e5L, 1e6L,
1e7L, 1e8L, 1e9L, 1e10L, 1e11L, 1e12L, 1e13L,
1e14L, 1e15L, 1e16L, 1e17L, 1e18L, 1e19L, 1e20L,
1e21L, 1e22L, 1e23L, 1e24L, 1e25L, 1e26L, 1e27L,
1e28L, 1e29L, 1e30L, 1e31L, 1e32L, 1e33L, 1e34L,
1e35L, 1e36L, 1e37L, 1e38L, 1e39L, 1e40L, 1e41L,
1e42L, 1e43L, 1e44L, 1e45L, 1e46L, 1e47L, 1e48L,
1e49L, 1e50L, 1e51L, 1e52L, 1e53L, 1e54L, 1e55L
};

#ifdef BOOST_CHARCONV_HAS_FLOAT128
static constexpr __float128 powers_of_tenq[] = {
1e0Q, 1e1Q, 1e2Q, 1e3Q, 1e4Q, 1e5Q, 1e6Q,
1e7Q, 1e8Q, 1e9Q, 1e10Q, 1e11Q, 1e12Q, 1e13Q,
1e14Q, 1e15Q, 1e16Q, 1e17Q, 1e18Q, 1e19Q, 1e20Q,
1e21Q, 1e22Q, 1e23Q, 1e24Q, 1e25Q, 1e26Q, 1e27Q,
1e28Q, 1e29Q, 1e30Q, 1e31Q, 1e32Q, 1e33Q, 1e34Q,
1e35Q, 1e36Q, 1e37Q, 1e38Q, 1e39Q, 1e40Q, 1e41Q,
1e42Q, 1e43Q, 1e44Q, 1e45Q, 1e46Q, 1e47Q, 1e48Q,
1e49Q, 1e50Q, 1e51Q, 1e52Q, 1e53Q, 1e54Q, 1e55Q
};
#endif

template <typename ResultType, typename Unsigned_Integer, typename ArrayPtr>
inline long double fast_path(std::int64_t q, Unsigned_Integer w, bool negative, ArrayPtr table) noexcept
{
long double return_val;
// The general idea is as follows.
// if 0 <= s <= 2^64 and if 10^0 <= p <= 10^27
// Both s and p can be represented exactly
// because of this s*p and s/p will produce
// correctly rounded values

auto ld = static_cast<ResultType>(w);

// At the absolute minimum and maximum rounding errors of 1 ULP can cause overflow
if (power == 4914 && i == UINT64_C(1189731495357231765))
if (q < 0)
{
return_val = std::numeric_limits<long double>::max();
ld /= table[-q];
}
else if (power == -4950 && i == UINT64_C(3362103143112093506))
else
{
return_val = std::numeric_limits<long double>::min();
ld *= table[q];
}
else

if (negative)
{
return_val = i * std::pow(10.0L, static_cast<long double>(power));
if (std::isinf(return_val))
{
success = false;
return negative ? -0.0L : 0.0L;
}
ld = -ld;
}

return_val = negative ? -return_val : return_val;
return ld;
}

success = true;
return return_val;
#ifdef BOOST_CHARCONV_HAS_FLOAT128
template <typename Unsigned_Integer>
inline __float128 compute_float128(std::int64_t q, Unsigned_Integer w, bool negative, std::errc& success) noexcept
{
// GLIBC uses 2^-16444 but MPFR uses 2^-16445 as the smallest subnormal value for 80 bit
// 39 is the max number of digits in an uint128_t
static constexpr auto smallest_power = -4951 - 39;
static constexpr auto largest_power = 4932;

if (-55 <= q && q <= 48 && w <= static_cast<Unsigned_Integer>(1) << 113)
{
success = std::errc();
return fast_path<__float128>(q, w, negative, powers_of_tenq);
}

if (w == 0)
{
success = std::errc();
return negative ? -0.0Q : 0.0Q;
}
else if (q > largest_power)
{
success = std::errc::result_out_of_range;
return negative ? -HUGE_VALQ : HUGE_VALQ;
}
else if (q < smallest_power)
{
success = std::errc::result_out_of_range;
return negative ? -0.0Q : 0.0Q;
}

success = std::errc::not_supported;
return 0;
}
#endif

template <typename ResultType, typename Unsigned_Integer>
inline ResultType compute_float80(std::int64_t q, Unsigned_Integer w, bool negative, std::errc& success) noexcept
{
// GLIBC uses 2^-16444 but MPFR uses 2^-16445 as the smallest subnormal value for 80 bit
// 39 is the max number of digits in an uint128_t
static constexpr auto smallest_power = -4951 - 39;
static constexpr auto largest_power = 4932;

// We start with a fast path
// It is an extension of what was described in Clinger WD.
// How to read floating point numbers accurately.
// ACM SIGPLAN Notices. 1990
// https://dl.acm.org/doi/pdf/10.1145/93542.93557
static constexpr auto clinger_max_exp = BOOST_CHARCONV_LDBL_BITS == 80 ? 27 : 48;
static constexpr auto clinger_min_exp = BOOST_CHARCONV_LDBL_BITS == 80 ? -34 : -55;

if (clinger_min_exp <= q && q <= clinger_max_exp && w <= static_cast<Unsigned_Integer>(1) << 113)
{
success = std::errc();
return fast_path<ResultType>(q, w, negative, powers_of_ten_ld);
}

if (w == 0)
{
success = std::errc();
return negative ? -0.0L : 0.0L;
}
else if (q > largest_power)
{
success = std::errc::result_out_of_range;
return negative ? -HUGE_VALL : HUGE_VALL;
}
else if (q < smallest_power)
{
success = std::errc::result_out_of_range;
return negative ? -0.0L : 0.0L;
}

success = std::errc::not_supported;
return 0;
}

#endif // BOOST_CHARCONV_LDBL_BITS > 64

}}} // Namespaces

#endif // BOOST_CHARCONV_DETAIL_COMPUTE_FLOAT80_HPP
Loading

0 comments on commit 43042ed

Please sign in to comment.