numerical_derivative_example.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. // Copyright Christopher Kormanyos 2013.
  2. // Distributed under the Boost Software License, Version 1.0.
  3. // (See accompanying file LICENSE_1_0.txt or
  4. // copy at http://www.boost.org/LICENSE_1_0.txt).
  5. #ifdef _MSC_VER
  6. # pragma warning (disable : 4996) // assignment operator could not be generated.
  7. #endif
  8. # include <iostream>
  9. # include <iomanip>
  10. # include <limits>
  11. # include <cmath>
  12. #include <boost/static_assert.hpp>
  13. #include <boost/type_traits/is_floating_point.hpp>
  14. #include <boost/math/special_functions/next.hpp> // for float_distance
  15. //[numeric_derivative_example
  16. /*`The following example shows how multiprecision calculations can be used to
  17. obtain full precision in a numerical derivative calculation that suffers from precision loss.
  18. Consider some well-known central difference rules for numerically
  19. computing the 1st derivative of a function [f'(x)] with [/x] real.
  20. Need a reference here? Introduction to Partial Differential Equations, Peter J. Olver
  21. December 16, 2012
  22. Here, the implementation uses a C++ template that can be instantiated with various
  23. floating-point types such as `float`, `double`, `long double`, or even
  24. a user-defined floating-point type like __multiprecision.
  25. We will now use the derivative template with the built-in type `double` in
  26. order to numerically compute the derivative of a function, and then repeat
  27. with a 5 decimal digit higher precision user-defined floating-point type.
  28. Consider the function shown below.
  29. !!
  30. (3)
  31. We will now take the derivative of this function with respect to x evaluated
  32. at x = 3= 2. In other words,
  33. (4)
  34. The expected result is
  35. 0:74535 59924 99929 89880 . (5)
  36. The program below uses the derivative template in order to perform
  37. the numerical calculation of this derivative. The program also compares the
  38. numerically-obtained result with the expected result and reports the absolute
  39. relative error scaled to a deviation that can easily be related to the number of
  40. bits of lost precision.
  41. */
  42. /*` [note Rquires the C++11 feature of
  43. [@http://en.wikipedia.org/wiki/Anonymous_function#C.2B.2B anonymous functions]
  44. for the derivative function calls like `[]( const double & x_) -> double`.
  45. */
  46. template <typename value_type, typename function_type>
  47. value_type derivative (const value_type x, const value_type dx, function_type function)
  48. {
  49. /*! \brief Compute the derivative of function using a 3-point central difference rule of O(dx^6).
  50. \tparam value_type, floating-point type, for example: `double` or `cpp_dec_float_50`
  51. \tparam function_type
  52. \param x Value at which to evaluate derivative.
  53. \param dx Incremental step-size.
  54. \param function Function whose derivative is to computed.
  55. \return derivative at x.
  56. */
  57. BOOST_STATIC_ASSERT_MSG(false == std::numeric_limits<value_type>::is_integer, "value_type must be a floating-point type!");
  58. const value_type dx2(dx * 2U);
  59. const value_type dx3(dx * 3U);
  60. // Difference terms.
  61. const value_type m1 ((function (x + dx) - function(x - dx)) / 2U);
  62. const value_type m2 ((function (x + dx2) - function(x - dx2)) / 4U);
  63. const value_type m3 ((function (x + dx3) - function(x - dx3)) / 6U);
  64. const value_type fifteen_m1 (m1 * 15U);
  65. const value_type six_m2 (m2 * 6U);
  66. const value_type ten_dx (dx * 10U);
  67. return ((fifteen_m1 - six_m2) + m3) / ten_dx; // Derivative.
  68. } //
  69. #include <boost/multiprecision/cpp_dec_float.hpp>
  70. using boost::multiprecision::number;
  71. using boost::multiprecision::cpp_dec_float;
  72. // Re-compute using 5 extra decimal digits precision (22) than double (17).
  73. #define MP_DIGITS10 unsigned (std::numeric_limits<double>::max_digits10 + 5)
  74. typedef cpp_dec_float<MP_DIGITS10> mp_backend;
  75. typedef number<mp_backend> mp_type;
  76. int main()
  77. {
  78. {
  79. const double d =
  80. derivative
  81. ( 1.5, // x = 3.2
  82. std::ldexp (1., -9), // step size 2^-9 = see below for choice.
  83. [](const double & x)->double // Function f(x).
  84. {
  85. return std::sqrt((x * x) - 1.) - std::acos(1. / x);
  86. }
  87. );
  88. // The 'exactly right' result is [sqrt]5 / 3 = 0.74535599249992989880.
  89. const double rel_error = (d - 0.74535599249992989880) / 0.74535599249992989880;
  90. const double bit_error = std::abs(rel_error) / std::numeric_limits<double>::epsilon();
  91. std::cout.precision (std::numeric_limits<double>::digits10); // Show all guaranteed decimal digits.
  92. std::cout << std::showpoint ; // Ensure that any trailing zeros are shown too.
  93. std::cout << " derivative : " << d << std::endl;
  94. std::cout << " expected : " << 0.74535599249992989880 << std::endl;
  95. // Can compute an 'exact' value using multiprecision type.
  96. std::cout << " expected : " << sqrt(static_cast<mp_type>(5))/3U << std::endl;
  97. std::cout << " bit_error : " << static_cast<unsigned long>(bit_error) << std::endl;
  98. std::cout.precision(6);
  99. std::cout << "float_distance = " << boost::math::float_distance(0.74535599249992989880, d) << std::endl;
  100. }
  101. { // Compute using multiprecision type with an extra 5 decimal digits of precision.
  102. const mp_type mp =
  103. derivative(mp_type(mp_type(3) / 2U), // x = 3/2
  104. mp_type(mp_type(1) / 10000000U), // Step size 10^7.
  105. [](const mp_type & x)->mp_type
  106. {
  107. return sqrt((x * x) - 1.) - acos (1. / x); // Function
  108. }
  109. );
  110. const double d = mp.convert_to<double>(); // Convert to closest double.
  111. const double rel_error = (d - 0.74535599249992989880) / 0.74535599249992989880;
  112. const double bit_error = std::abs (rel_error) / std::numeric_limits<double>::epsilon();
  113. std::cout.precision (std::numeric_limits <double>::digits10); // All guaranteed decimal digits.
  114. std::cout << std::showpoint ; // Ensure that any trailing zeros are shown too.
  115. std::cout << " derivative : " << d << std::endl;
  116. // Can compute an 'exact' value using multiprecision type.
  117. std::cout << " expected : " << sqrt(static_cast<mp_type>(5))/3U << std::endl;
  118. std::cout << " expected : " << 0.74535599249992989880
  119. << std::endl;
  120. std::cout << " bit_error : " << static_cast<unsigned long>(bit_error) << std::endl;
  121. std::cout.precision(6);
  122. std::cout << "float_distance = " << boost::math::float_distance(0.74535599249992989880, d) << std::endl;
  123. }
  124. } // int main()
  125. /*`
  126. The result of this program on a system with an eight-byte, 64-bit IEEE-754
  127. conforming floating-point representation for `double` is:
  128. derivative : 0.745355992499951
  129. derivative : 0.745355992499943
  130. expected : 0.74535599249993
  131. bit_error : 78
  132. derivative : 0.745355992499930
  133. expected : 0.745355992499930
  134. bit_error : 0
  135. The resulting bit error is 0. This means that the result of the derivative
  136. calculation is bit-identical with the double representation of the expected result,
  137. and this is the best result possible for the built-in type.
  138. The derivative in this example has a known closed form. There are, however,
  139. countless situations in numerical analysis (and not only for numerical deriva-
  140. tives) for which the calculation at hand does not have a known closed-form
  141. solution or for which the closed-form solution is highly inconvenient to use. In
  142. such cases, this technique may be useful.
  143. This example has shown how multiprecision can be used to add extra digits
  144. to an ill-conditioned calculation that suffers from precision loss. When the result
  145. of the multiprecision calculation is converted to a built-in type such as double,
  146. the entire precision of the result in double is preserved.
  147. */
  148. /*
  149. Description: Autorun "J:\Cpp\big_number\Debug\numerical_derivative_example.exe"
  150. derivative : 0.745355992499943
  151. expected : 0.745355992499930
  152. expected : 0.745355992499930
  153. bit_error : 78
  154. float_distance = 117.000
  155. derivative : 0.745355992499930
  156. expected : 0.745355992499930
  157. expected : 0.745355992499930
  158. bit_error : 0
  159. float_distance = 0.000000
  160. */