2

Is it possible to limit a value in a given range, between min and max, using only arithmetic? That is, + - x / and %?

I am not able to use functions such as min, max nor IF-statements.

Let's assume I have a range of [1850, 1880], for any values < 1850, it should display 1850. For values > 1880, 1880 should be displayed. It would also be acceptable if only 1850 was displayed outside the range.

I tried:

x = (((x - xmax) % (xmax - xmin)) + (xmax - xmin)) % (xmax - xmin) + xmin 

but it gives different values in the middle of the range for values lower than xmin.

John Coleman
  • 51,337
  • 7
  • 54
  • 119
Qliver
  • 31
  • 7
  • "number" is ambiguous in computer science. What type of number are you talking about? Signed integer? Fixed-length unsigned? Double? Please clarify. Also -- if you have a programming language in mind, please stipulate. The operators `/` and `%` have different semantics in different programming languages. – John Coleman Nov 07 '18 at 11:31
  • @JohnColeman It's the IEC 61131-3 languages and the data type is ANY_INT. To keep it simple let's just say that my question was about a signed integer. By "/" I mean division and by "%" I mean the modulo operation, which in my case is actually "MOD" – Qliver Nov 07 '18 at 11:47
  • 2
    So by "division" you mean "integer (truncating) division"? Also -- how does `%` work with negative values (since you are talking about signed integers)? This is one area where there is a [surprising amount of variation](https://en.wikipedia.org/wiki/Modulo_operation) between various programming languages – John Coleman Nov 07 '18 at 11:56
  • 2
    @JohnColeman That's right, integer truncating division. As for `%`, in my case the result has the same sign as the dividend. – Qliver Nov 07 '18 at 12:19
  • why no `if`? Can you use the ternary operator? Division (and possibly also multiplication, especially on embedded systems) is likely far more expensive than a simple jump – phuclv Nov 27 '18 at 12:21
  • @phuclv the limitation is obviously not performance. Since it has a PLC tag I bet it's because the manufacturer for some stupid reason cripled one of the HMI or PLC scripting function to those arithmic operators. – JerMah Nov 29 '18 at 11:56
  • @JerMah I saw the [tag:plc] tag, that's why I mentioned embedded systems above. I've never worked with PLCs so I don't know it's instruction set, but any Turing-complete architecture must have a way to loop – phuclv Nov 29 '18 at 12:20
  • 1
    @phuclv The thing about a PLC is, yes, deep down it's a microcontroller, but you only work in the IDE of the manufacturer and you are forced to eat what ever they are cooking, and it isn't ANSI C. Ofcourse in the standard PLC languages IFs and compares are available, even in the most early PLCs. Which is why I suspect it's some fringe case where he is forced to use a 'calculate' function block or something. – JerMah Nov 29 '18 at 12:24
  • @phuclv There is no `IF` available. It's because the vendor's HMI scripting (HTML 5 based) does not support anything but arithmetic in its current state. – Qliver Nov 29 '18 at 12:25
  • It's Siemens WinCC 7.x WebUX isn't it? – JerMah Nov 29 '18 at 19:43

3 Answers3

5

If you know the size of the integer type, you can extract its sign bit (assuming two's complement) using integer division:

// Example in C
int sign_bit(int s) 
{
    // cast to unsigned (important)
    unsigned u = (unsigned)s;

    // number of bits in int
    // if your integer size is fixed, this is just a constant
    static const unsigned b = sizeof(int) * 8;

    // pow(2, b - 1)
    // again, a constant which can be pre-computed
    static const unsigned p = 1 << (b - 1);

    // use integer division to get top bit
    return (int)(u / p);
}

This returns 1 if s < 0 and 0 otherwise; it can be used to calculate the absolute value:

int abs_arith(int v)
{
    // sign bit
    int b = sign_bit(v);

    // actual sign (+1 / -1)
    int s = 1 - 2 * b;

    // sign(v) * v = abs(v)
    return s * v;
}

The desired function looks like this:

enter image description here

It is useful to first shift the minimum to zero:

enter image description here

This function form can be computed as a sum of the two shifted absolute value functions below:

enter image description here

However the resultant function is scaled by a factor of 2; shifting to zero helps here because we only need to divide by 2, and shift back to the original minimum:

// Example in C
int clamp_minmax(int val, int min, int max)
{
    // range length
    int range = max - min;

    // shift minimum to zero
    val = val - min;

    // blue function
    int blue = abs_arith(val);

    // green function
    int green = range - abs_arith(val - range);

    // add and divide by 2
    val = (blue + green) / 2;        

    // shift to original minimum
    return val + min;
}

This solution, although satisfies the requirements of the problem, is limited to signed integer types (and languages which allow integer overflow - I'm unsure of how this could be overcome in e.g. Java).

phuclv
  • 37,963
  • 15
  • 156
  • 475
meowgoesthedog
  • 14,670
  • 4
  • 27
  • 40
  • 1
    This is impressive (+1) although given the ambiguity in the original question, it is impossible to tell if it satisfies whatever unstated constraints they have on the solution. – John Coleman Nov 07 '18 at 11:35
  • @JohnColeman Indeed, I'm aware of the rather restrictive assumptions made by this solution. I'll try to think of a more general one in the meantime. – meowgoesthedog Nov 07 '18 at 11:41
  • I suspect that without a lot of constraints a solution is impossible. – John Coleman Nov 07 '18 at 11:48
3

I found this while messing around in... excel. It only works for strictly positive integers. Although this is not more restrictive as the answer by meowgoesthedog because he also effectivly halves the integer space by dividing by two at the end. It doesn't use mod.

//A = 1 if x <= min
//A = 0 if x >= min
A = 1-(min-min/x)/min 

//B = 0 if x <= max
//B = 1 if x > max
B = (max-max/x)/max

x = A*min + (1-A)*(1-B)*x + B*max
JerMah
  • 693
  • 5
  • 17
  • 2
    I did a few tests and this works well for all possible values in my application, except for `0`. which gives the maximum of the range. But it's something that I can live with, since I can initialize the `x` variable with a `value > 0`. – Qliver Nov 29 '18 at 11:44
  • 1
    I suggest shifting the while thing up first by 16k or something and then back down by 16k. That way it works for 0 and negatives aswell. (Don't forget to shift the min and max values aswell.) – JerMah Nov 29 '18 at 11:47
-2

I found this solution in Python:

A = -1  # Minimum value
B = +1  # Maximum value
x = min(max(x, A), B)