temporal: alternative solution to overflow in timeline operator*()

This uses boost::multiprecision::int512_t when multiplying and dividing by the numerator
and denominator of a ratio_t. 128 bits would be sufficient but for some reason, the boost
docs show the 512 bit variant being very slightly faster.

This is a better solution than using a double, which although it will prevent overflow
has fairly limited resolution.
This commit is contained in:
Paul Davis 2022-05-24 21:46:10 -06:00
parent 540a15efa0
commit 60e5b84d78
2 changed files with 36 additions and 5 deletions

View File

@ -359,9 +359,8 @@ ControlList::list_merge (ControlList const& other, boost::function<double(double
void
ControlList::_x_scale (ratio_t const & factor)
{
double double_factor = (double)factor;
for (iterator i = _events.begin(); i != _events.end(); ++i) {
(*i)->when = timepos_t::from_superclock ((*i)->when.val() * double_factor + 0.5);
(*i)->when = (*i)->when.operator* (factor);
}
mark_dirty ();

View File

@ -21,6 +21,8 @@
#include <exception>
#include <sstream>
#include <boost/multiprecision/cpp_int.hpp>
#include "pbd/enumwriter.h"
#include "pbd/error.h"
#include "pbd/compose.h"
@ -200,8 +202,23 @@ timecnt_t::compute_beats() const
timecnt_t
timecnt_t::operator*(ratio_t const & r) const
{
const int62_t v (_distance.flagged(), int_div_round (_distance.val() * r.numerator(), r.denominator()));
return timecnt_t (v, _position);
boost::multiprecision::int512_t bignum = _distance.val();
bignum *= r.numerator ();
bignum /= r.denominator ();
try {
int64_t midnum = bignum.convert_to<int64_t> ();
assert (midnum < int62_t::max);
const int62_t v (_distance.flagged(), midnum);
return timecnt_t (v, _position);
} catch (...) {
fatal << X_("arithmetic overflow in timeline math\n") << endmsg;
/* NOTREACHED */
return timecnt_t ();
}
}
ratio_t
@ -589,7 +606,22 @@ timepos_t
timepos_t::operator*(ratio_t const & n) const
{
/* this cannot make the value negative, since ratio_t is always positive */
return timepos_t (is_beats(), int_div_round (val() * n.numerator(), n.denominator()));
boost::multiprecision::int512_t bignum = val();
bignum *= n.numerator ();
bignum /= n.denominator ();
try {
int64_t midnum = bignum.convert_to<int64_t> ();
assert (midnum < int62_t::max);
return timepos_t (is_beats(), midnum);
} catch (...) {
fatal << X_("arithmetic overflow in timepos_t::operator* (ratio_t)\n") << endmsg;
/* NOTREACHED */
return timepos_t();
}
}
timepos_t &