Browse Source

Rescale values to better utilize R128 range before snapping

Aaron Franke 1 week ago
parent
commit
0ad409bcd6
1 changed files with 32 additions and 15 deletions
  1. 32 15
      scene/gui/range.cpp

+ 32 - 15
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;
 }