ulps_plot.hpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. // (C) Copyright Nick Thompson 2020.
  2. // Use, modification and distribution are subject to the
  3. // Boost Software License, Version 1.0. (See accompanying file
  4. // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  5. #ifndef BOOST_MATH_TOOLS_ULP_PLOT_HPP
  6. #define BOOST_MATH_TOOLS_ULP_PLOT_HPP
  7. #include <algorithm>
  8. #include <iostream>
  9. #include <iomanip>
  10. #include <cassert>
  11. #include <vector>
  12. #include <utility>
  13. #include <fstream>
  14. #include <string>
  15. #include <list>
  16. #include <random>
  17. #include <stdexcept>
  18. #include <boost/math/tools/condition_numbers.hpp>
  19. #include <boost/random/uniform_real_distribution.hpp>
  20. #include <boost/algorithm/string/predicate.hpp>
  21. // Design of this function comes from:
  22. // https://blogs.mathworks.com/cleve/2017/01/23/ulps-plots-reveal-math-function-accurary/
  23. // The envelope is the maximum of 1/2 and half the condition number of function evaluation.
  24. namespace boost::math::tools {
  25. namespace detail {
  26. template<class F1, class F2, class CoarseReal, class PreciseReal>
  27. void write_gridlines(std::ostream& fs, int horizontal_lines, int vertical_lines,
  28. F1 x_scale, F2 y_scale, CoarseReal min_x, CoarseReal max_x, PreciseReal min_y, PreciseReal max_y,
  29. int graph_width, int graph_height, int margin_left, std::string const & font_color)
  30. {
  31. // Make a grid:
  32. for (int i = 1; i <= horizontal_lines; ++i) {
  33. PreciseReal y_cord_dataspace = min_y + ((max_y - min_y)*i)/horizontal_lines;
  34. auto y = y_scale(y_cord_dataspace);
  35. fs << "<line x1='0' y1='" << y << "' x2='" << graph_width
  36. << "' y2='" << y
  37. << "' stroke='gray' stroke-width='1' opacity='0.5' stroke-dasharray='4' />\n";
  38. fs << "<text x='" << -margin_left/4 + 5 << "' y='" << y - 3
  39. << "' font-family='times' font-size='10' fill='" << font_color << "' transform='rotate(-90 "
  40. << -margin_left/4 + 8 << " " << y + 5 << ")'>"
  41. << std::setprecision(4) << y_cord_dataspace << "</text>\n";
  42. }
  43. for (int i = 1; i <= vertical_lines; ++i) {
  44. CoarseReal x_cord_dataspace = min_x + ((max_x - min_x)*i)/vertical_lines;
  45. CoarseReal x = x_scale(x_cord_dataspace);
  46. fs << "<line x1='" << x << "' y1='0' x2='" << x
  47. << "' y2='" << graph_height
  48. << "' stroke='gray' stroke-width='1' opacity='0.5' stroke-dasharray='4' />\n";
  49. fs << "<text x='" << x - 10 << "' y='" << graph_height + 10
  50. << "' font-family='times' font-size='10' fill='" << font_color << "'>"
  51. << std::setprecision(4) << x_cord_dataspace << "</text>\n";
  52. }
  53. }
  54. }
  55. template<class F, typename PreciseReal, typename CoarseReal>
  56. class ulps_plot {
  57. public:
  58. ulps_plot(F hi_acc_impl, CoarseReal a, CoarseReal b,
  59. size_t samples = 1000, bool perturb_abscissas = false, int random_seed = -1);
  60. ulps_plot& clip(PreciseReal clip);
  61. ulps_plot& width(int width);
  62. ulps_plot& envelope_color(std::string const & color);
  63. ulps_plot& title(std::string const & title);
  64. ulps_plot& background_color(std::string const & background_color);
  65. ulps_plot& font_color(std::string const & font_color);
  66. ulps_plot& crop_color(std::string const & color);
  67. ulps_plot& nan_color(std::string const & color);
  68. ulps_plot& ulp_envelope(bool write_ulp);
  69. template<class G>
  70. ulps_plot& add_fn(G g, std::string const & color = "steelblue");
  71. ulps_plot& horizontal_lines(int horizontal_lines);
  72. ulps_plot& vertical_lines(int vertical_lines);
  73. void write(std::string const & filename) const;
  74. friend std::ostream& operator<<(std::ostream& fs, ulps_plot const & plot)
  75. {
  76. using std::abs;
  77. using std::floor;
  78. using std::isnan;
  79. if (plot.ulp_list_.size() == 0)
  80. {
  81. throw std::domain_error("No functions added for comparison.");
  82. }
  83. if (plot.width_ <= 1)
  84. {
  85. throw std::domain_error("Width = " + std::to_string(plot.width_) + ", which is too small.");
  86. }
  87. PreciseReal worst_ulp_distance = 0;
  88. PreciseReal min_y = std::numeric_limits<PreciseReal>::max();
  89. PreciseReal max_y = std::numeric_limits<PreciseReal>::lowest();
  90. for (auto const & ulp_vec : plot.ulp_list_)
  91. {
  92. for (auto const & ulp : ulp_vec)
  93. {
  94. if (static_cast<PreciseReal>(abs(ulp)) > worst_ulp_distance)
  95. {
  96. worst_ulp_distance = static_cast<PreciseReal>(abs(ulp));
  97. }
  98. if (static_cast<PreciseReal>(ulp) < min_y)
  99. {
  100. min_y = static_cast<PreciseReal>(ulp);
  101. }
  102. if (static_cast<PreciseReal>(ulp) > max_y)
  103. {
  104. max_y = static_cast<PreciseReal>(ulp);
  105. }
  106. }
  107. }
  108. // half-ulp accuracy is the best that can be expected; sometimes we can get less, but barely less.
  109. // then the axes don't show up; painful!
  110. if (max_y < 0.5) {
  111. max_y = 0.5;
  112. }
  113. if (min_y > -0.5) {
  114. min_y = -0.5;
  115. }
  116. if (plot.clip_ > 0)
  117. {
  118. if (max_y > plot.clip_)
  119. {
  120. max_y = plot.clip_;
  121. }
  122. if (min_y < -plot.clip_)
  123. {
  124. min_y = -plot.clip_;
  125. }
  126. }
  127. int height = static_cast<int>(floor(double(plot.width_)/1.61803));
  128. int margin_top = 40;
  129. int margin_left = 25;
  130. if (plot.title_.size() == 0)
  131. {
  132. margin_top = 10;
  133. margin_left = 15;
  134. }
  135. int margin_bottom = 20;
  136. int margin_right = 20;
  137. int graph_height = height - margin_bottom - margin_top;
  138. int graph_width = plot.width_ - margin_left - margin_right;
  139. // Maps [a,b] to [0, graph_width]
  140. auto x_scale = [&](CoarseReal x)->CoarseReal
  141. {
  142. return ((x-plot.a_)/(plot.b_ - plot.a_))*static_cast<CoarseReal>(graph_width);
  143. };
  144. auto y_scale = [&](PreciseReal y)->PreciseReal
  145. {
  146. return ((max_y - y)/(max_y - min_y) )*static_cast<PreciseReal>(graph_height);
  147. };
  148. fs << "<?xml version=\"1.0\" encoding='UTF-8' ?>\n"
  149. << "<svg xmlns='http://www.w3.org/2000/svg' width='"
  150. << plot.width_ << "' height='"
  151. << height << "'>\n"
  152. << "<style>\nsvg { background-color:" << plot.background_color_ << "; }\n"
  153. << "</style>\n";
  154. if (plot.title_.size() > 0)
  155. {
  156. fs << "<text x='" << floor(plot.width_/2)
  157. << "' y='" << floor(margin_top/2)
  158. << "' font-family='Palatino' font-size='25' fill='"
  159. << plot.font_color_ << "' alignment-baseline='middle' text-anchor='middle'>"
  160. << plot.title_
  161. << "</text>\n";
  162. }
  163. // Construct SVG group to simplify the calculations slightly:
  164. fs << "<g transform='translate(" << margin_left << ", " << margin_top << ")'>\n";
  165. // y-axis:
  166. fs << "<line x1='0' y1='0' x2='0' y2='" << graph_height
  167. << "' stroke='gray' stroke-width='1'/>\n";
  168. PreciseReal x_axis_loc = y_scale(static_cast<PreciseReal>(0));
  169. fs << "<line x1='0' y1='" << x_axis_loc
  170. << "' x2='" << graph_width << "' y2='" << x_axis_loc
  171. << "' stroke='gray' stroke-width='1'/>\n";
  172. if (worst_ulp_distance > 3)
  173. {
  174. detail::write_gridlines(fs, plot.horizontal_lines_, plot.vertical_lines_, x_scale, y_scale, plot.a_, plot.b_,
  175. min_y, max_y, graph_width, graph_height, margin_left, plot.font_color_);
  176. }
  177. else
  178. {
  179. std::vector<double> ys{-3.0, -2.5, -2.0, -1.5, -1.0, -0.5, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0};
  180. for (size_t i = 0; i < ys.size(); ++i)
  181. {
  182. if (min_y <= ys[i] && ys[i] <= max_y)
  183. {
  184. PreciseReal y_cord_dataspace = ys[i];
  185. PreciseReal y = y_scale(y_cord_dataspace);
  186. fs << "<line x1='0' y1='" << y << "' x2='" << graph_width
  187. << "' y2='" << y
  188. << "' stroke='gray' stroke-width='1' opacity='0.5' stroke-dasharray='4' />\n";
  189. fs << "<text x='" << -margin_left/2 << "' y='" << y - 3
  190. << "' font-family='times' font-size='10' fill='" << plot.font_color_ << "' transform='rotate(-90 "
  191. << -margin_left/2 + 7 << " " << y << ")'>"
  192. << std::setprecision(4) << y_cord_dataspace << "</text>\n";
  193. }
  194. }
  195. for (int i = 1; i <= plot.vertical_lines_; ++i)
  196. {
  197. CoarseReal x_cord_dataspace = plot.a_ + ((plot.b_ - plot.a_)*i)/plot.vertical_lines_;
  198. CoarseReal x = x_scale(x_cord_dataspace);
  199. fs << "<line x1='" << x << "' y1='0' x2='" << x
  200. << "' y2='" << graph_height
  201. << "' stroke='gray' stroke-width='1' opacity='0.5' stroke-dasharray='4' />\n";
  202. fs << "<text x='" << x - 10 << "' y='" << graph_height + 10
  203. << "' font-family='times' font-size='10' fill='" << plot.font_color_ << "'>"
  204. << std::setprecision(4) << x_cord_dataspace << "</text>\n";
  205. }
  206. }
  207. int color_idx = 0;
  208. for (auto const & ulp : plot.ulp_list_)
  209. {
  210. std::string color = plot.colors_[color_idx++];
  211. for (size_t j = 0; j < ulp.size(); ++j)
  212. {
  213. if (isnan(ulp[j]))
  214. {
  215. if(plot.nan_color_ == "")
  216. continue;
  217. CoarseReal x = x_scale(plot.coarse_abscissas_[j]);
  218. PreciseReal y = y_scale(static_cast<PreciseReal>(plot.clip_));
  219. fs << "<circle cx='" << x << "' cy='" << y << "' r='1' fill='" << plot.nan_color_ << "'/>\n";
  220. y = y_scale(static_cast<PreciseReal>(-plot.clip_));
  221. fs << "<circle cx='" << x << "' cy='" << y << "' r='1' fill='" << plot.nan_color_ << "'/>\n";
  222. }
  223. if (plot.clip_ > 0 && static_cast<PreciseReal>(abs(ulp[j])) > plot.clip_)
  224. {
  225. if (plot.crop_color_ == "")
  226. continue;
  227. CoarseReal x = x_scale(plot.coarse_abscissas_[j]);
  228. PreciseReal y = y_scale(static_cast<PreciseReal>(ulp[j] < 0 ? -plot.clip_ : plot.clip_));
  229. fs << "<circle cx='" << x << "' cy='" << y << "' r='1' fill='" << plot.crop_color_ << "'/>\n";
  230. }
  231. else
  232. {
  233. CoarseReal x = x_scale(plot.coarse_abscissas_[j]);
  234. PreciseReal y = y_scale(static_cast<PreciseReal>(ulp[j]));
  235. fs << "<circle cx='" << x << "' cy='" << y << "' r='1' fill='" << color << "'/>\n";
  236. }
  237. }
  238. }
  239. if (plot.ulp_envelope_)
  240. {
  241. std::string close_path = "' stroke='" + plot.envelope_color_ + "' stroke-width='1' fill='none'></path>\n";
  242. size_t jstart = 0;
  243. while (plot.cond_[jstart] > max_y)
  244. {
  245. ++jstart;
  246. if (jstart >= plot.cond_.size())
  247. {
  248. goto done;
  249. }
  250. }
  251. size_t jmin = jstart;
  252. new_top_path:
  253. if (jmin >= plot.cond_.size())
  254. {
  255. goto start_bottom_paths;
  256. }
  257. fs << "<path d='M" << x_scale(plot.coarse_abscissas_[jmin]) << " " << y_scale(plot.cond_[jmin]);
  258. for (size_t j = jmin + 1; j < plot.coarse_abscissas_.size(); ++j)
  259. {
  260. bool bad = isnan(plot.cond_[j]) || (plot.cond_[j] > max_y);
  261. if (bad)
  262. {
  263. ++j;
  264. while ( (j < plot.coarse_abscissas_.size() - 2) && bad)
  265. {
  266. bad = isnan(plot.cond_[j]) || (plot.cond_[j] > max_y);
  267. ++j;
  268. }
  269. jmin = j;
  270. fs << close_path;
  271. goto new_top_path;
  272. }
  273. CoarseReal t = x_scale(plot.coarse_abscissas_[j]);
  274. PreciseReal y = y_scale(plot.cond_[j]);
  275. fs << " L" << t << " " << y;
  276. }
  277. fs << close_path;
  278. start_bottom_paths:
  279. jmin = jstart;
  280. new_bottom_path:
  281. if (jmin >= plot.cond_.size())
  282. {
  283. goto done;
  284. }
  285. fs << "<path d='M" << x_scale(plot.coarse_abscissas_[jmin]) << " " << y_scale(-plot.cond_[jmin]);
  286. for (size_t j = jmin + 1; j < plot.coarse_abscissas_.size(); ++j)
  287. {
  288. bool bad = isnan(plot.cond_[j]) || (-plot.cond_[j] < min_y);
  289. if (bad)
  290. {
  291. ++j;
  292. while ( (j < plot.coarse_abscissas_.size() - 2) && bad)
  293. {
  294. bad = isnan(plot.cond_[j]) || (-plot.cond_[j] < min_y);
  295. ++j;
  296. }
  297. jmin = j;
  298. fs << close_path;
  299. goto new_bottom_path;
  300. }
  301. CoarseReal t = x_scale(plot.coarse_abscissas_[j]);
  302. PreciseReal y = y_scale(-plot.cond_[j]);
  303. fs << " L" << t << " " << y;
  304. }
  305. fs << close_path;
  306. }
  307. done:
  308. fs << "</g>\n"
  309. << "</svg>\n";
  310. return fs;
  311. }
  312. private:
  313. std::vector<PreciseReal> precise_abscissas_;
  314. std::vector<CoarseReal> coarse_abscissas_;
  315. std::vector<PreciseReal> precise_ordinates_;
  316. std::vector<PreciseReal> cond_;
  317. std::list<std::vector<CoarseReal>> ulp_list_;
  318. std::vector<std::string> colors_;
  319. CoarseReal a_;
  320. CoarseReal b_;
  321. PreciseReal clip_;
  322. int width_;
  323. std::string envelope_color_;
  324. bool ulp_envelope_;
  325. int horizontal_lines_;
  326. int vertical_lines_;
  327. std::string title_;
  328. std::string background_color_;
  329. std::string font_color_;
  330. std::string crop_color_;
  331. std::string nan_color_;
  332. };
  333. template<class F, typename PreciseReal, typename CoarseReal>
  334. ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::envelope_color(std::string const & color)
  335. {
  336. envelope_color_ = color;
  337. return *this;
  338. }
  339. template<class F, typename PreciseReal, typename CoarseReal>
  340. ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::clip(PreciseReal clip)
  341. {
  342. clip_ = clip;
  343. return *this;
  344. }
  345. template<class F, typename PreciseReal, typename CoarseReal>
  346. ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::width(int width)
  347. {
  348. width_ = width;
  349. return *this;
  350. }
  351. template<class F, typename PreciseReal, typename CoarseReal>
  352. ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::horizontal_lines(int horizontal_lines)
  353. {
  354. horizontal_lines_ = horizontal_lines;
  355. return *this;
  356. }
  357. template<class F, typename PreciseReal, typename CoarseReal>
  358. ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::vertical_lines(int vertical_lines)
  359. {
  360. vertical_lines_ = vertical_lines;
  361. return *this;
  362. }
  363. template<class F, typename PreciseReal, typename CoarseReal>
  364. ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::title(std::string const & title)
  365. {
  366. title_ = title;
  367. return *this;
  368. }
  369. template<class F, typename PreciseReal, typename CoarseReal>
  370. ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::background_color(std::string const & background_color)
  371. {
  372. background_color_ = background_color;
  373. return *this;
  374. }
  375. template<class F, typename PreciseReal, typename CoarseReal>
  376. ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::font_color(std::string const & font_color)
  377. {
  378. font_color_ = font_color;
  379. return *this;
  380. }
  381. template<class F, typename PreciseReal, typename CoarseReal>
  382. ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::crop_color(std::string const & color)
  383. {
  384. crop_color_ = color;
  385. return *this;
  386. }
  387. template<class F, typename PreciseReal, typename CoarseReal>
  388. ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::nan_color(std::string const & color)
  389. {
  390. nan_color_ = color;
  391. return *this;
  392. }
  393. template<class F, typename PreciseReal, typename CoarseReal>
  394. ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::ulp_envelope(bool write_ulp_envelope)
  395. {
  396. ulp_envelope_ = write_ulp_envelope;
  397. return *this;
  398. }
  399. template<class F, typename PreciseReal, typename CoarseReal>
  400. void ulps_plot<F, PreciseReal, CoarseReal>::write(std::string const & filename) const
  401. {
  402. if (!boost::algorithm::ends_with(filename, ".svg"))
  403. {
  404. throw std::logic_error("Only svg files are supported at this time.");
  405. }
  406. std::ofstream fs(filename);
  407. fs << *this;
  408. fs.close();
  409. }
  410. template<class F, typename PreciseReal, typename CoarseReal>
  411. ulps_plot<F, PreciseReal, CoarseReal>::ulps_plot(F hi_acc_impl, CoarseReal a, CoarseReal b,
  412. size_t samples, bool perturb_abscissas, int random_seed) : crop_color_("red")
  413. {
  414. // Use digits10 for this comparison in case the two types have differeing radixes:
  415. static_assert(std::numeric_limits<PreciseReal>::digits10 >= std::numeric_limits<CoarseReal>::digits10, "PreciseReal must have higher precision that CoarseReal");
  416. if (samples < 10)
  417. {
  418. throw std::domain_error("Must have at least 10 samples, samples = " + std::to_string(samples));
  419. }
  420. if (b <= a)
  421. {
  422. throw std::domain_error("On interval [a,b], b > a is required.");
  423. }
  424. a_ = a;
  425. b_ = b;
  426. std::mt19937_64 gen;
  427. if (random_seed == -1)
  428. {
  429. std::random_device rd;
  430. gen.seed(rd());
  431. }
  432. // Boost's uniform_real_distribution can generate quad and multiprecision random numbers; std's cannot:
  433. boost::random::uniform_real_distribution<PreciseReal> dis(static_cast<PreciseReal>(a), static_cast<PreciseReal>(b));
  434. precise_abscissas_.resize(samples);
  435. coarse_abscissas_.resize(samples);
  436. if (perturb_abscissas)
  437. {
  438. for(size_t i = 0; i < samples; ++i)
  439. {
  440. precise_abscissas_[i] = dis(gen);
  441. }
  442. std::sort(precise_abscissas_.begin(), precise_abscissas_.end());
  443. for (size_t i = 0; i < samples; ++i)
  444. {
  445. coarse_abscissas_[i] = static_cast<CoarseReal>(precise_abscissas_[i]);
  446. }
  447. }
  448. else
  449. {
  450. for(size_t i = 0; i < samples; ++i)
  451. {
  452. coarse_abscissas_[i] = static_cast<CoarseReal>(dis(gen));
  453. }
  454. std::sort(coarse_abscissas_.begin(), coarse_abscissas_.end());
  455. for (size_t i = 0; i < samples; ++i)
  456. {
  457. precise_abscissas_[i] = static_cast<PreciseReal>(coarse_abscissas_[i]);
  458. }
  459. }
  460. precise_ordinates_.resize(samples);
  461. for (size_t i = 0; i < samples; ++i)
  462. {
  463. precise_ordinates_[i] = hi_acc_impl(precise_abscissas_[i]);
  464. }
  465. cond_.resize(samples, std::numeric_limits<PreciseReal>::quiet_NaN());
  466. for (size_t i = 0 ; i < samples; ++i)
  467. {
  468. PreciseReal y = precise_ordinates_[i];
  469. if (y != 0)
  470. {
  471. // Maybe cond_ is badly names; should it be half_cond_?
  472. cond_[i] = boost::math::tools::evaluation_condition_number(hi_acc_impl, precise_abscissas_[i])/2;
  473. // Half-ULP accuracy is the correctly rounded result, so make sure the envelop doesn't go below this:
  474. if (cond_[i] < 0.5)
  475. {
  476. cond_[i] = 0.5;
  477. }
  478. }
  479. // else leave it as nan.
  480. }
  481. clip_ = -1;
  482. width_ = 1100;
  483. envelope_color_ = "chartreuse";
  484. ulp_envelope_ = true;
  485. horizontal_lines_ = 8;
  486. vertical_lines_ = 10;
  487. title_ = "";
  488. background_color_ = "black";
  489. font_color_ = "white";
  490. }
  491. template<class F, typename PreciseReal, typename CoarseReal>
  492. template<class G>
  493. ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::add_fn(G g, std::string const & color)
  494. {
  495. using std::abs;
  496. size_t samples = precise_abscissas_.size();
  497. std::vector<CoarseReal> ulps(samples);
  498. for (size_t i = 0; i < samples; ++i)
  499. {
  500. PreciseReal y_hi_acc = precise_ordinates_[i];
  501. PreciseReal y_lo_acc = static_cast<PreciseReal>(g(coarse_abscissas_[i]));
  502. PreciseReal absy = abs(y_hi_acc);
  503. PreciseReal dist = static_cast<PreciseReal>(nextafter(static_cast<CoarseReal>(absy), std::numeric_limits<CoarseReal>::max()) - static_cast<CoarseReal>(absy));
  504. ulps[i] = static_cast<CoarseReal>((y_lo_acc - y_hi_acc)/dist);
  505. }
  506. ulp_list_.emplace_back(ulps);
  507. colors_.emplace_back(color);
  508. return *this;
  509. }
  510. } // namespace boost::math::tools
  511. #endif