From 97d1f454901115c3f625f4cafd8229fa6950218e Mon Sep 17 00:00:00 2001 From: Tilmann Date: Sun, 30 Jun 2024 13:42:39 +0200 Subject: [PATCH] Fix root-node precision problems --- .../index/qthypercube2/QuadTreeKD2.java | 2 +- .../org/tinspin/index/util/MathTools.java | 16 +++++++-- .../java/org/tinspin/util/MathToolsTest.java | 35 +++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/tinspin/index/qthypercube2/QuadTreeKD2.java b/src/main/java/org/tinspin/index/qthypercube2/QuadTreeKD2.java index b9e63db..01d50d4 100644 --- a/src/main/java/org/tinspin/index/qthypercube2/QuadTreeKD2.java +++ b/src/main/java/org/tinspin/index/qthypercube2/QuadTreeKD2.java @@ -132,7 +132,7 @@ private void adjustRootSize(double[] key) { for (int i = 0; i < root.getValueCount(); i++) { dMax = Math.max(dMax, MathTools.maxDelta(root.getValues()[i].point(), root.getCenter())); } - double radius = MathTools.floorPowerOfTwo(dMax) * 2; + double radius = MathTools.ceilPowerOfTwo(dMax + QUtil.EPS_MUL); if (radius > 0) { root.adjustRadius(radius); } else if (root.getValueCount() >= maxNodeSize - 1) { diff --git a/src/main/java/org/tinspin/index/util/MathTools.java b/src/main/java/org/tinspin/index/util/MathTools.java index 0a61ab0..9fe89dd 100644 --- a/src/main/java/org/tinspin/index/util/MathTools.java +++ b/src/main/java/org/tinspin/index/util/MathTools.java @@ -21,11 +21,23 @@ public class MathTools { private MathTools() {} + /** + * Similar to Math.ceil() with the ceiling being the next higher power of 2. + * The resulting number can repeatedly and (almost) always be divided by two without loss of precision. + * @param d input + * @return next power of two above or equal to 'input' + */ + public static double ceilPowerOfTwo(double d) { + double ceil = floorPowerOfTwo(d); + return ceil == d ? ceil : ceil * 2; + } + /** * Similar to Math.floor() with the floor being the next lower power of 2. - * The resulting number can repeatedly (almost) always be divided by two without loss of precision. + * The resulting number can repeatedly and (almost) always be divided by two without loss of precision. + * We calculate the "floor" by setting the "fraction" of the bit representation to 0. * @param d input - * @return next lower power of two below 'input' + * @return next power of two below or equal to 'input' */ public static double floorPowerOfTwo(double d) { // Set fraction to "0". diff --git a/src/test/java/org/tinspin/util/MathToolsTest.java b/src/test/java/org/tinspin/util/MathToolsTest.java index 3e1507a..c33150f 100644 --- a/src/test/java/org/tinspin/util/MathToolsTest.java +++ b/src/test/java/org/tinspin/util/MathToolsTest.java @@ -25,6 +25,28 @@ public class MathToolsTest { + @Test + public void powerOfTwoCeil() { + assertEquals(1./32., MathTools.ceilPowerOfTwo(0.03), 0.0); + assertEquals(0.5, MathTools.ceilPowerOfTwo(0.3), 0.0); + assertEquals(4, MathTools.ceilPowerOfTwo(3), 0.0); + assertEquals(32, MathTools.ceilPowerOfTwo(30), 0.0); + assertEquals(512, MathTools.ceilPowerOfTwo(300), 0.0); + + assertEquals(-0.5, MathTools.ceilPowerOfTwo(-0.3), 0.0); + assertEquals(-4, MathTools.ceilPowerOfTwo(-3), 0.0); + assertEquals(-32, MathTools.ceilPowerOfTwo(-30), 0.0); + + // identity + assertEquals(0, MathTools.ceilPowerOfTwo(0), 0.0); + assertEquals(-0.5, MathTools.ceilPowerOfTwo(-0.5), 0.0); + assertEquals(0.5, MathTools.ceilPowerOfTwo(0.5), 0.0); + assertEquals(-1, MathTools.ceilPowerOfTwo(-1), 0.0); + assertEquals(1, MathTools.ceilPowerOfTwo(1), 0.0); + assertEquals(-2, MathTools.ceilPowerOfTwo(-2), 0.0); + assertEquals(2, MathTools.ceilPowerOfTwo(2), 0.0); + } + @Test public void powerOfTwoFloor() { assertEquals(1./64., MathTools.floorPowerOfTwo(0.03), 0.0); @@ -32,6 +54,19 @@ public void powerOfTwoFloor() { assertEquals(2, MathTools.floorPowerOfTwo(3), 0.0); assertEquals(16, MathTools.floorPowerOfTwo(30), 0.0); assertEquals(256, MathTools.floorPowerOfTwo(300), 0.0); + + assertEquals(-0.25, MathTools.floorPowerOfTwo(-0.3), 0.0); + assertEquals(-2, MathTools.floorPowerOfTwo(-3), 0.0); + assertEquals(-16, MathTools.floorPowerOfTwo(-30), 0.0); + + // identity + assertEquals(0, MathTools.ceilPowerOfTwo(0), 0.0); + assertEquals(-0.5, MathTools.ceilPowerOfTwo(-0.5), 0.0); + assertEquals(0.5, MathTools.ceilPowerOfTwo(0.5), 0.0); + assertEquals(-1, MathTools.ceilPowerOfTwo(-1), 0.0); + assertEquals(1, MathTools.ceilPowerOfTwo(1), 0.0); + assertEquals(-2, MathTools.ceilPowerOfTwo(-2), 0.0); + assertEquals(2, MathTools.ceilPowerOfTwo(2), 0.0); } @Test