Skip to content

Commit

Permalink
Fix up cpp_bin_float for very small bit counts. (#577)
Browse files Browse the repository at this point in the history
Fix up cpp_bin_float for very small bit counts.
Adds support for emulating float16_t and bfloat16_t.
Also adds test cases, and updates test_arithmetic.hpp to cope with testing small bit count types.
Fixes #576.
  • Loading branch information
jzmaddock authored Dec 16, 2023
1 parent a32f657 commit 2018773
Show file tree
Hide file tree
Showing 5 changed files with 363 additions and 129 deletions.
72 changes: 60 additions & 12 deletions include/boost/multiprecision/cpp_bin_float.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,13 @@ class cpp_bin_float

#ifdef BOOST_HAS_FLOAT128
template <class Float>
typename std::enable_if<std::is_same<Float, float128_type>::value, cpp_bin_float&>::type assign_float(Float f)
typename std::enable_if<std::is_same<Float, float128_type>::value && (std::numeric_limits<Float>::digits > Digits), cpp_bin_float&>::type assign_float(Float f)
{
cpp_bin_float<113, DigitBase, Allocator, Exponent, MinExponent, MaxExponent> bf(f);
return *this = bf;
}
template <class Float>
typename std::enable_if<std::is_same<Float, float128_type>::value && (std::numeric_limits<Float>::digits <= Digits), cpp_bin_float&>::type assign_float(Float f)
{
using default_ops::eval_add;
using bf_int_type = typename boost::multiprecision::detail::canonical<int, cpp_bin_float>::type;
Expand Down Expand Up @@ -337,7 +343,7 @@ class cpp_bin_float
m_sign = false;
m_exponent = 0;

constexpr std::ptrdiff_t bits = sizeof(int) * CHAR_BIT - 1;
constexpr std::ptrdiff_t bits = sizeof(int) * CHAR_BIT - 1 < MaxExponent - 1 ? sizeof(int) * CHAR_BIT - 1 : 3;
int e;
f = frexpq(f, &e);
while (f)
Expand All @@ -352,15 +358,36 @@ class cpp_bin_float
eval_add(*this, t);
}
m_exponent += static_cast<Exponent>(e);
if (m_exponent > max_exponent)
{
m_exponent = exponent_infinity;
m_data = static_cast<ui_type>(0u);
}
else if (m_exponent < min_exponent)
{
m_exponent = exponent_zero;
m_data = static_cast<ui_type>(0u);
}
return *this;
}
#endif
#ifdef BOOST_HAS_FLOAT128
template <class Float>
typename std::enable_if<std::is_floating_point<Float>::value && !std::is_same<Float, float128_type>::value, cpp_bin_float&>::type assign_float(Float f)
typename std::enable_if<std::is_floating_point<Float>::value && !std::is_same<Float, float128_type>::value && (std::numeric_limits<Float>::digits > Digits), cpp_bin_float&>::type assign_float(Float f)
#else
template <class Float>
typename std::enable_if<std::is_floating_point<Float>::value, cpp_bin_float&>::type assign_float(Float f)
typename std::enable_if<std::is_floating_point<Float>::value && (std::numeric_limits<Float>::digits > Digits), cpp_bin_float&>::type assign_float(Float f)
#endif
{
cpp_bin_float<std::numeric_limits<Float>::digits, DigitBase, Allocator, Exponent, MinExponent, MaxExponent> bf(f);
return *this = bf;
}
#ifdef BOOST_HAS_FLOAT128
template <class Float>
typename std::enable_if<std::is_floating_point<Float>::value && !std::is_same<Float, float128_type>::value && (std::numeric_limits<Float>::digits <= Digits), cpp_bin_float&>::type assign_float(Float f)
#else
template <class Float>
typename std::enable_if<std::is_floating_point<Float>::value && (std::numeric_limits<Float>::digits <= Digits), cpp_bin_float&>::type assign_float(Float f)
#endif
{
using std::frexp;
Expand Down Expand Up @@ -399,7 +426,13 @@ class cpp_bin_float
m_sign = false;
m_exponent = 0;

constexpr std::ptrdiff_t bits = sizeof(int) * CHAR_BIT - 1;
//
// This code picks off the bits in f a few at a time and injects them into *this.
// It does not do roundingm so we must have more digits precision in *this than
// in the floating point value (the normal situation, unless we're emulating another
// type like float16_t).
//
constexpr std::ptrdiff_t bits = static_cast<std::ptrdiff_t>(sizeof(int) * CHAR_BIT - 1) < static_cast<std::ptrdiff_t>(MaxExponent - 1) ? static_cast<std::ptrdiff_t>(sizeof(int) * CHAR_BIT - 1) : 3;
int e;
f = frexp(f, &e);
while (f != static_cast<Float>(0.0F))
Expand All @@ -414,6 +447,16 @@ class cpp_bin_float
eval_add(*this, t);
}
m_exponent += static_cast<Exponent>(e);
if (m_exponent > max_exponent)
{
m_exponent = exponent_infinity;
m_data = static_cast<ui_type>(0u);
}
else if(m_exponent < min_exponent)
{
m_exponent = exponent_zero;
m_data = static_cast<ui_type>(0u);
}
return *this;
}

Expand Down Expand Up @@ -514,7 +557,12 @@ class cpp_bin_float
using ar_type = typename boost::multiprecision::detail::canonical<ui_type, rep_type>::type;
m_data = static_cast<ar_type>(fi);
std::size_t shift = msb(fi);
if (shift >= bit_count)
if (shift > max_exponent)
{
m_exponent = exponent_infinity;
m_data = static_cast<limb_type>(0);
}
else if (shift >= bit_count)
{
m_exponent = static_cast<Exponent>(shift);
m_data = static_cast<ar_type>(fi >> (shift + 1 - bit_count));
Expand All @@ -524,7 +572,7 @@ class cpp_bin_float
m_exponent = static_cast<Exponent>(shift);
eval_left_shift(m_data, bit_count - shift - 1);
}
BOOST_MP_ASSERT(eval_bit_test(m_data, bit_count - 1));
BOOST_MP_ASSERT((m_exponent == exponent_infinity) || eval_bit_test(m_data, bit_count - 1));
m_sign = detail::is_negative(i);
}
return *this;
Expand Down Expand Up @@ -1281,7 +1329,7 @@ inline void eval_divide(cpp_bin_float<Digits, DigitBase, Allocator, Exponent, Mi

template <unsigned Digits, digit_base_type DigitBase, class Allocator, class Exponent, Exponent MinE, Exponent MaxE,
class Allocator2, class Exponent2, Exponent MinE2, Exponent MaxE2, class U>
inline typename std::enable_if<boost::multiprecision::detail::is_unsigned<U>::value>::type eval_divide(cpp_bin_float<Digits, DigitBase, Allocator, Exponent, MinE, MaxE>& res,
inline typename std::enable_if<boost::multiprecision::detail::is_unsigned<U>::value && (std::numeric_limits<U>::digits <= Digits)>::type eval_divide(cpp_bin_float<Digits, DigitBase, Allocator, Exponent, MinE, MaxE>& res,
const cpp_bin_float<Digits, DigitBase, Allocator2, Exponent2, MinE2, MaxE2>& u, const U& v)
{
#ifdef BOOST_MSVC
Expand Down Expand Up @@ -1396,14 +1444,14 @@ inline typename std::enable_if<boost::multiprecision::detail::is_unsigned<U>::va
}

template <unsigned Digits, digit_base_type DigitBase, class Allocator, class Exponent, Exponent MinE, Exponent MaxE, class U>
inline typename std::enable_if<boost::multiprecision::detail::is_unsigned<U>::value>::type eval_divide(cpp_bin_float<Digits, DigitBase, Allocator, Exponent, MinE, MaxE>& res, const U& v)
inline typename std::enable_if<boost::multiprecision::detail::is_unsigned<U>::value && (std::numeric_limits<U>::digits <= Digits)>::type eval_divide(cpp_bin_float<Digits, DigitBase, Allocator, Exponent, MinE, MaxE>& res, const U& v)
{
eval_divide(res, res, v);
}

template <unsigned Digits, digit_base_type DigitBase, class Allocator, class Exponent, Exponent MinE, Exponent MaxE,
class Allocator2, class Exponent2, Exponent MinE2, Exponent MaxE2, class S>
inline typename std::enable_if<boost::multiprecision::detail::is_signed<S>::value && boost::multiprecision::detail::is_integral<S>::value>::type eval_divide(cpp_bin_float<Digits, DigitBase, Allocator, Exponent, MinE, MaxE>& res,
inline typename std::enable_if<boost::multiprecision::detail::is_signed<S>::value && boost::multiprecision::detail::is_integral<S>::value && (std::numeric_limits<S>::digits <= Digits)>::type eval_divide(cpp_bin_float<Digits, DigitBase, Allocator, Exponent, MinE, MaxE>& res,
const cpp_bin_float<Digits, DigitBase, Allocator2, Exponent2, MinE2, MaxE2>& u, const S& v)
{
using ui_type = typename boost::multiprecision::detail::make_unsigned<S>::type;
Expand All @@ -1413,7 +1461,7 @@ inline typename std::enable_if<boost::multiprecision::detail::is_signed<S>::valu
}

template <unsigned Digits, digit_base_type DigitBase, class Allocator, class Exponent, Exponent MinE, Exponent MaxE, class S>
inline typename std::enable_if<boost::multiprecision::detail::is_signed<S>::value && boost::multiprecision::detail::is_integral<S>::value>::type eval_divide(cpp_bin_float<Digits, DigitBase, Allocator, Exponent, MinE, MaxE>& res, const S& v)
inline typename std::enable_if<boost::multiprecision::detail::is_signed<S>::value && boost::multiprecision::detail::is_integral<S>::value && (std::numeric_limits<S>::digits <= Digits)>::type eval_divide(cpp_bin_float<Digits, DigitBase, Allocator, Exponent, MinE, MaxE>& res, const S& v)
{
eval_divide(res, res, v);
}
Expand Down Expand Up @@ -1658,7 +1706,7 @@ inline typename std::enable_if<std::is_floating_point<Float>::value>::type eval_
//
// Perform rounding first, then afterwards extract the digits:
//
cpp_bin_float<static_cast<unsigned>(float_digits), digit_base_2, Allocator, Exponent, MinE, MaxE> arg;
cpp_bin_float<static_cast<unsigned>(float_digits), digit_base_2, Allocator, Exponent, 0, 0> arg;
typename cpp_bin_float<Digits, DigitBase, Allocator, Exponent, MinE, MaxE>::rep_type bits(original_arg.bits());
arg.exponent() = original_arg.exponent();
copy_and_round(arg, bits, (std::ptrdiff_t)digits_to_round_to);
Expand Down
2 changes: 2 additions & 0 deletions test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ test-suite arithmetic_tests :
[ run test_arithmetic_cpp_bin_float_3.cpp no_eh_support : : : <toolset>msvc:<cxxflags>-bigobj [ check-target-builds ../config//has_float128 : <source>quadmath ] ]
[ run test_arithmetic_cpp_bin_float_4.cpp no_eh_support : : : <toolset>msvc:<cxxflags>-bigobj [ check-target-builds ../config//has_float128 : <source>quadmath ] ]
[ run test_arithmetic_cpp_bin_float_5.cpp no_eh_support : : : <toolset>msvc:<cxxflags>-bigobj [ check-target-builds ../config//has_float128 : <source>quadmath ] ]
[ run test_arithmetic_cpp_bin_float_6.cpp no_eh_support : : : <toolset>msvc:<cxxflags>-bigobj [ check-target-builds ../config//has_float128 : <source>quadmath ] [ requires cxx17_if_constexpr ] ]

[ run test_arithmetic_mpf_50.cpp gmp no_eh_support : : : [ check-target-builds ../config//has_gmp : : <build>no ] [ check-target-builds ../config//has_float128 : <source>quadmath ] ]
[ run test_arithmetic_mpf.cpp gmp no_eh_support : : : [ check-target-builds ../config//has_gmp : : <build>no ] [ check-target-builds ../config//has_float128 : <source>quadmath ] ]
Expand Down Expand Up @@ -1232,6 +1233,7 @@ test-suite misc :
[ run git_issue_526.cpp ]
[ run git_issue_540.cpp ]
[ run git_issue_573.cpp : : : <toolset>msvc:<cxxflags>-sdl ]
[ run git_issue_576.cpp : : : [ check-target-builds ../config//has_float128 : <define>TEST_FLOAT128 <source>quadmath : ] ]
[ compile git_issue_98.cpp :
[ check-target-builds ../config//has_float128 : <define>TEST_FLOAT128 <source>quadmath : ]
[ check-target-builds ../config//has_gmp : <define>TEST_GMP <source>gmp : ]
Expand Down
122 changes: 122 additions & 0 deletions test/git_issue_576.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright 2023 John Maddock. Distributed under the Boost
// Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/multiprecision/cpp_bin_float.hpp>
#include <iostream>
#include "test.hpp"

template <class Float, class F>
void test(F f, bool big)
{
Float f2(f);
if (big)
{
F tol = f * static_cast<F>(std::numeric_limits<Float>::epsilon());
F max = static_cast<F>((std::numeric_limits<Float>::max)());
F diff = static_cast<F>(f2) - f;
if (diff < 0)
diff = -diff;
if (f > max)
{
BOOST_CHECK(isinf(f2) || (diff < tol));
}
else
{
BOOST_CHECK_LE(diff, tol);
}
}
else
{
BOOST_CHECK_EQUAL(static_cast<F>(f2), f);
}
}


template <class Float>
void test()
{
std::int64_t i = static_cast<std::int64_t>(1) << (std::numeric_limits<Float>::max_exponent + 1);
Float f(i);
BOOST_CHECK_EQUAL(f, std::numeric_limits<Float>::infinity());

if (std::numeric_limits<Float>::max_exponent < std::numeric_limits<float>::digits)
{
BOOST_CHECK_EQUAL(Float(static_cast<float>(i)), std::numeric_limits<Float>::infinity());
}
if (std::numeric_limits<Float>::max_exponent < std::numeric_limits<double>::digits)
{
BOOST_CHECK_EQUAL(Float(static_cast<double>(i)), std::numeric_limits<Float>::infinity());
}
if (std::numeric_limits<Float>::max_exponent < std::numeric_limits<long double>::digits)
{
BOOST_CHECK_EQUAL(Float(static_cast<long double>(i)), std::numeric_limits<Float>::infinity());
}
#ifdef BOOST_HAS_FLOAT128
if (std::numeric_limits<Float>::max_exponent < std::numeric_limits<__float128>::digits)
{
BOOST_CHECK_EQUAL(Float(static_cast<__float128>(i)), std::numeric_limits<Float>::infinity());
}
#endif

--i;

while (i)
{
Float f2(i);
BOOST_CHECK_NE(f2, std::numeric_limits<Float>::infinity());
bool big = boost::multiprecision::msb(i) >= std::numeric_limits<Float>::digits;
if (big)
{
BOOST_CHECK_LE(static_cast<std::int64_t>(f2), i);
}
else
{
BOOST_CHECK_EQUAL(static_cast<std::int64_t>(f2), i);
}

if (std::numeric_limits<Float>::max_exponent < std::numeric_limits<float>::digits)
{
test<Float>(static_cast<float>(i), big);
for (int exp = -1; exp >= std::numeric_limits<Float>::min_exponent; --exp)
test<Float>(std::ldexp(static_cast<float>(i), exp), big);
}
if (std::numeric_limits<Float>::max_exponent < std::numeric_limits<double>::digits)
{
test<Float>(static_cast<double>(i), big);
for (int exp = -1; exp >= std::numeric_limits<Float>::min_exponent; --exp)
test<Float>(std::ldexp(static_cast<double>(i), exp), big);
}
if (std::numeric_limits<Float>::max_exponent < std::numeric_limits<long double>::digits)
{
test<Float>(static_cast<long double>(i), big);
for (int exp = -1; exp >= std::numeric_limits<Float>::min_exponent; --exp)
test<Float>(std::ldexp(static_cast<long double>(i), exp), big);
}
#ifdef BOOST_HAS_FLOAT128
if (std::numeric_limits<Float>::max_exponent < std::numeric_limits<__float128>::digits)
{
test<Float>(static_cast<__float128>(i), big);
for (int exp = -1; exp >= std::numeric_limits<Float>::min_exponent; --exp)
test<Float>(ldexpq(static_cast<__float128>(i), exp), big);
}
#endif

--i;
}
}



int main()
{
using namespace boost::multiprecision;
typedef number<backends::cpp_bin_float<11, backends::digit_base_2, void, std::int16_t, -14, 15>, et_off> float16_t;
//typedef number<backends::cpp_bin_float<8, backends::digit_base_2, void, std::int16_t, -126, 127>, et_off> bfloat16_t;

test<float16_t>();
//test<bfloat16_t>();

return boost::report_errors();
}
Loading

0 comments on commit 2018773

Please sign in to comment.