diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index b3d9b17a02e..6c80ff997ba 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -33,22 +33,39 @@ #include "thirdparty/misc/r128.h" double Range::_snapped_r128(double p_value, double p_step) { - if (p_step != 0) { - // All these lines are the equivalent of: p_value = Math::floor(p_value / p_step + 0.5) * p_step; - // Convert to String to force rounding to a decimal value (not a binary one). - String step_str = String::num(p_step); - String value_str = String::num(p_value); - R128 step_r128; - R128 value_r128; - const R128 half_r128 = R128(0.5); - r128FromString(&step_r128, step_str.ascii().get_data(), nullptr); - r128FromString(&value_r128, value_str.ascii().get_data(), nullptr); - r128Div(&value_r128, &value_r128, &step_r128); - r128Add(&value_r128, &value_r128, &half_r128); - r128Floor(&value_r128, &value_r128); - r128Mul(&value_r128, &value_r128, &step_r128); - p_value = value_r128; + if (p_step == 0.0) { + return p_value; } + if (p_value > (1e18 * p_step) || p_value < -(1e18 * p_step)) { + // If the value goes outside of the range R128 supports, fallback to normal snapping. + return Math::snapped(p_value, p_step); + } + // Rescale values to better utilize R128's range before snapping. + // R128 is fixed-precision with 64 bits after the decimal point, but double already uses 53 of those, + // so a step size finer than 2^-11 will lose precision, and in practice even 1e-3 can be problematic. + // By rescaling the value and step, we can shift precision into the higher bits (effectively turning R128 into a makeshift float). + const int decimals = 14 - Math::floor(std::log10(MAX(Math::abs(p_value), p_step))); + const double scale = Math::pow(10.0, decimals); + p_value *= scale; + p_step *= scale; + // All these lines are the equivalent of: p_value = Math::floor(p_value / p_step + 0.5) * p_step; + // Convert to String to force rounding to a decimal value (not a binary one). + String step_str = String::num(p_step); + String value_str = String::num(p_value); + R128 step_r128; + R128 value_r128; + const R128 half_r128 = R128(0.5); + r128FromString(&step_r128, step_str.ascii().get_data(), nullptr); + r128FromString(&value_r128, value_str.ascii().get_data(), nullptr); + r128Div(&value_r128, &value_r128, &step_r128); + r128Add(&value_r128, &value_r128, &half_r128); + r128Floor(&value_r128, &value_r128); + r128Mul(&value_r128, &value_r128, &step_r128); + if (scale != 1.0) { + const R128 scale_r128 = R128(scale); + r128Div(&value_r128, &value_r128, &scale_r128); + } + p_value = (double)value_r128; return p_value; }