Search code examples
algorithmnumbersroundingclamp

Algorithm for rounding and clamping a number to specific ends


I'm looking for an efficient algorithm that clamps an integer value within a range, while also changing its base10 end to one of multiple, variable length ends.

This should clamp a value between the min and max, while changing the end to one of the specific ends defined in ends, as close as possible to the original value.

Example input data:

ends: [26, 201, 330] (sorted list of integers, at least a factor 10 smaller than <value>)
min: 5350
max: 7392

value: 2000 -> 5426
value: 6205 -> 6201
value: 7400 -> 7330

I'll implement this in Go, but any pseudocode or other imperative language is also appreciated.


Solution

  • I guess you know how to clamp:

    clamp(n, min, max) = max(min, min(max, n))
    

    Then to remove the end and replace it with what it should end, you need the next higher power of 10:

    nextPowerOf10(n) {
        if (n == 0)
            return 10    // mathematically not correct but works here
        x = 1
        while (n > 0) {
            x = x * 10
            n = n / 10    // integer division
        }
        return x
    }
    

    So replacing the end of a number becomes simple:

    replace(n, end) = n - n % nextPowerOf10(end) + end
    

    Then you can check the other candidates on both sides, so when m = replace(clamp(n, min, max), end) and p = nextPowerOf10(end), the other candidates are: m - p and m + p, everything put together:

    clampEnd(n, min, max, end) {
        m = clamp(n, min, max)
        p = nextPowerOf10(end)
        m = m - m % p + end
        diff = -1
        ret = -1
        for (x in [m - p, m, m + p]) {
            // this rounds down, make abs(n - x) <= diff to round up on equal distance
            if (x >= min && x <= max && (diff < 0 || abs(n - x) < diff)) {
                diff = abs(n - x)
                ret = x
            }
        }
        return ret    // returns -1 if no number in range ends with end
    }
    

    Examples:

    • 2000: m = clamp(2000, 5350, 7392) = 5350, p = nextPowerOf10(26) = 100, m = m - m % p + end = 5350 - 5350 % 100 + 26 = 5326, candidates = [m - p, m, m + p] = [5226, 5326, 5426], result = 5426 (only one in range).
    • 6205: candidates = [5201, 6201, 7201] => 6201
    • 7400: candidates = [6330, 7330, 8330] => 7330