// (C) Copyright Nick Thompson 2020. // (C) Copyright John Maddock 2020. // Use, modification and distribution are subject to 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 #include #include #include #include #include using boost::math::tools::ulps_plot; #if 0 namespace boost { namespace multiprecision { namespace detail { template struct transcendental_reduction_type { typedef T type; }; template struct transcendental_reduction_type > { typedef cpp_bin_float::bit_count * 3, digit_base_2, Allocator, Exponent, MinExponent, MaxExponent> type; }; } namespace default_ops { template void reduce_n_half_pi(T& arg, const T& n, bool go_down) { typedef typename boost::multiprecision::detail::transcendental_reduction_type::type reduction_type; reduction_type big_arg(arg); reduction_type reduction = get_constant_pi(); eval_ldexp(reduction, reduction, -1); // divide by 2 eval_multiply(reduction, n); BOOST_MATH_INSTRUMENT_CODE(big_arg.str(10, std::ios_base::scientific)); BOOST_MATH_INSTRUMENT_CODE(reduction.str(10, std::ios_base::scientific)); if (go_down) eval_subtract(big_arg, reduction, big_arg); else eval_subtract(big_arg, reduction); arg = big_arg; BOOST_MATH_INSTRUMENT_CODE(big_arg.str(10, std::ios_base::scientific)); BOOST_MATH_INSTRUMENT_CODE(arg.str(10, std::ios_base::scientific)); } template void _eval_sin(T& result, const T& x) { BOOST_STATIC_ASSERT_MSG(number_category::value == number_kind_floating_point, "The sin function is only valid for floating point types."); BOOST_MATH_INSTRUMENT_CODE(x.str(0, std::ios_base::scientific)); if (&result == &x) { T temp; eval_sin(temp, x); result = temp; return; } typedef typename boost::multiprecision::detail::canonical::type si_type; typedef typename boost::multiprecision::detail::canonical::type ui_type; typedef typename mpl::front::type fp_type; switch (eval_fpclassify(x)) { case FP_INFINITE: case FP_NAN: if (std::numeric_limits >::has_quiet_NaN) { result = std::numeric_limits >::quiet_NaN().backend(); errno = EDOM; } else BOOST_THROW_EXCEPTION(std::domain_error("Result is undefined or complex and there is no NaN for this number type.")); return; case FP_ZERO: result = x; return; default:; } // Local copy of the argument T xx = x; // Analyze and prepare the phase of the argument. // Make a local, positive copy of the argument, xx. // The argument xx will be reduced to 0 <= xx <= pi/2. bool b_negate_sin = false; if (eval_get_sign(x) < 0) { xx.negate(); b_negate_sin = !b_negate_sin; } T n_pi, t; T half_pi = get_constant_pi(); eval_ldexp(half_pi, half_pi, -1); // divide by 2 // Remove multiples of pi/2. if (xx.compare(half_pi) > 0) { eval_divide(n_pi, xx, half_pi); eval_trunc(n_pi, n_pi); t = ui_type(4); eval_fmod(t, n_pi, t); bool b_go_down = false; if (t.compare(ui_type(1)) == 0) { b_go_down = true; } else if (t.compare(ui_type(2)) == 0) { b_negate_sin = !b_negate_sin; } else if (t.compare(ui_type(3)) == 0) { b_negate_sin = !b_negate_sin; b_go_down = true; } if (b_go_down) eval_increment(n_pi); // // If n_pi is > 1/epsilon, then it is no longer an exact integer value // but an approximation. As a result we can no longer reliably reduce // xx to 0 <= xx < pi/2, nor can we tell the sign of the result as we need // n_pi % 4 for that, but that will always be zero in this situation. // We could use a higher precision type for n_pi, along with division at // higher precision, but that's rather expensive. So for now we do not support // this, and will see if anyone complains and has a legitimate use case. // if (n_pi.compare(get_constant_one_over_epsilon()) > 0) { BOOST_THROW_EXCEPTION(std::runtime_error("Argument reduction failed in call to sin")); result = ui_type(0); return; } reduce_n_half_pi(xx, n_pi, b_go_down); BOOST_MATH_INSTRUMENT_CODE(xx.str(0, std::ios_base::scientific)); BOOST_MATH_INSTRUMENT_CODE(n_pi.str(0, std::ios_base::scientific)); BOOST_ASSERT(xx.compare(half_pi) < 0); BOOST_ASSERT(xx.compare(ui_type(0)) >= 0); } t = half_pi; eval_subtract(t, xx); const bool b_zero = eval_get_sign(xx) == 0; const bool b_pi_half = eval_get_sign(t) == 0; BOOST_MATH_INSTRUMENT_CODE(xx.str(0, std::ios_base::scientific)); BOOST_MATH_INSTRUMENT_CODE(t.str(0, std::ios_base::scientific)); // Check if the reduced argument is very close to 0 or pi/2. const bool b_near_zero = xx.compare(fp_type(1e-1)) < 0; const bool b_near_pi_half = t.compare(fp_type(1e-1)) < 0; if (b_zero) { result = ui_type(0); } else if (b_pi_half) { result = ui_type(1); } else if (b_near_zero) { eval_multiply(t, xx, xx); eval_divide(t, si_type(-4)); T t2; t2 = fp_type(1.5); hyp0F1(result, t2, t); BOOST_MATH_INSTRUMENT_CODE(result.str(0, std::ios_base::scientific)); eval_multiply(result, xx); } else if (b_near_pi_half) { eval_multiply(t, t); eval_divide(t, si_type(-4)); T t2; t2 = fp_type(0.5); hyp0F1(result, t2, t); BOOST_MATH_INSTRUMENT_CODE(result.str(0, std::ios_base::scientific)); } else { // Scale to a small argument for an efficient Taylor series, // implemented as a hypergeometric function. Use a standard // divide by three identity a certain number of times. // Here we use division by 3^9 --> (19683 = 3^9). static const si_type n_scale = 9; static const si_type n_three_pow_scale = static_cast(19683L); eval_divide(xx, n_three_pow_scale); // Now with small arguments, we are ready for a series expansion. eval_multiply(t, xx, xx); eval_divide(t, si_type(-4)); T t2; t2 = fp_type(1.5); hyp0F1(result, t2, t); BOOST_MATH_INSTRUMENT_CODE(result.str(0, std::ios_base::scientific)); eval_multiply(result, xx); // Convert back using multiple angle identity. for (boost::int32_t k = static_cast(0); k < n_scale; k++) { // Rescale the cosine value using the multiple angle identity. eval_multiply(t2, result, ui_type(3)); eval_multiply(t, result, result); eval_multiply(t, result); eval_multiply(t, ui_type(4)); eval_subtract(result, t2, t); } } if (b_negate_sin) result.negate(); BOOST_MATH_INSTRUMENT_CODE(result.str(0, std::ios_base::scientific)); } }}} // namespace boost::multiprecision::default_ops #endif int main() { using PreciseReal = boost::multiprecision::mpfr_float_100; using CoarseReal = boost::multiprecision::cpp_bin_float_50; typedef boost::math::policies::policy< boost::math::policies::promote_float, boost::math::policies::promote_double > no_promote_policy; auto ai_coarse = [](CoarseReal const& x)->CoarseReal { return sin(x); }; auto ai_precise = [](PreciseReal const& x)->PreciseReal { return sin(x); }; /* for (CoarseReal val = 1e45; ; val *= 4) { CoarseReal found = ai_coarse(val); CoarseReal expect = CoarseReal(ai_precise(PreciseReal(val))); std::cout << std::setw(30) << val << std::setw(30) << boost::math::relative_difference(found, expect) << std::endl; } */ std::string filename = "cpp_bin_float_sin.svg"; int samples = 100000; // How many pixels wide do you want your .svg? int width = 700; // Near a root, we have unbounded relative error. So for functions with roots, we define an ULP clip: PreciseReal clip = 50; // Should we perturb the abscissas? i.e., should we compute the high precision function f at x, // and the low precision function at the nearest representable x̂ to x? // Or should we compute both the high precision and low precision function at a low precision representable x̂? bool perturb_abscissas = false; auto plot = ulps_plot(ai_precise, CoarseReal(-20), CoarseReal(20), samples, perturb_abscissas); // Note the argument chaining: plot./*clip(clip).*/width(width); plot.background_color("white").font_color("black"); // Sometimes it's useful to set a title, but in many cases it's more useful to just use a caption. //std::string title = "Airy Ai ULP plot at " + boost::core::demangle(typeid(CoarseReal).name()) + " precision"; //plot.title(title); plot.vertical_lines(6); plot.add_fn(ai_coarse); // You can write the plot to a stream: //std::cout << plot; // Or to a file: plot.write(filename); }