Flow value in the range [min, max] without separation

Is there a way in C # to wrap a given value of x between x_min and x_max. The value should not be clamped, as in Math.Min/Max , but wrapped as a float module.

The way to implement this:

 x = x - (x_max - x_min) * floor( x / (x_max - x_min)); 

However, I am wondering if there is a C # algorithm or method that implements the same functionality without partitions and without the probable problems with floating point restrictions that can occur when the value is far from the desired range.

+9
source share
9 answers

You can wrap it using two modulo operations , which are still equivalent to division . I do not think there is a more efficient way to do this without suggesting something about x .

 x = (((x - x_min) % (x_max - x_min)) + (x_max - x_min)) % (x_max - x_min) + x_min; 

The additional sum and module by the formula are intended to handle cases when x is actually less than x_min and the module may turn out to be negative. Or you can do it with if and one modular unit:

 if (x < x_min) x = x_max - (x_min - x) % (x_max - x_min); else x = x_min + (x - x_min) % (x_max - x_min); 

If only x is close to x_min and x_max and is accessible with very few sums or subtractions (think also about spreading errors), I think the module is your only available method.

No division

Bearing in mind that error propagation can become relevant, we can do this with a loop:

 d = x_max - x_min; if (abs(d) < MINIMUM_PRECISION) { return x_min; // Actually a divide by zero error :-) } while (x < x_min) { x += d; } while (x > x_max) { x -= d; } 

Probability Note

Using modular arithmetic has some statistical implications (floating point arithmetic will also have other meanings).

For example, suppose we wrap a random value between 0 and 5 that is included (for example, the result of a hex dice game) in the range [0,1] (that is, tossing a coin). Then

 0 -> 0 1 -> 1 2 -> 0 3 -> 1 4 -> 0 5 -> 1 

if the input has a flat spectrum, i.e. each number (0-5) has a probability of 1/6, the output will also be flat, and each element will have a probability of 3/6 = 50%.

But if we had a five-sided dice (0-4), or if we had a random number from 0 to 32767 and wanted to reduce it in the range (0, 99) to get a percentage, the result would not be flat, and some the numbers will be slightly (or not so little) more likely than others. In the case of a five-sided dice game with a throw, the coins of the head against the tails would be 60% -40%. In the case of 32767 percent, a percentage below 67 will be CEIL (32767/100) / FLOOR (32767/100) = 0.3% more likely than the rest.

(To see this more clearly, consider the number from β€œ00000” to β€œ32767”: after every 328 throws, the first three digits of the number will be β€œ327.” When this happens, the last two digits can go only from β€œ00” to β€œ67”, they cannot be from β€œ68” to β€œ99” because 32768 is out of range, so the numbers from 00 to 67 are slightly less likely.

Thus, if someone wanted to get a flat output, he would have to make sure that (max-min) is a divisor of the input range. In the case of 32767 and 100, the input range should be cut to the nearest hundred (minus one), 32699, so that (0-32699) contains 32700 results. Whenever the input was> = 32700, the input function would have to be called again to get a new value:

 function reduced() { #ifdef RECURSIVE int x = get_random(); if (x > MAX_ALLOWED) { return reduced(); // Retry } #else for (;;) { int x = get_random(); int d = x_max - x_min; if (x > MAX_ALLOWED) { continue; // Retry } return x_min + ( ( (x - x_min) % d ) + d ) % d; } #endif 

When the value of (INPUTRANGE% OUTPUTRANGE) / (INPUTRANGE) is significant, the overhead can be significant (for example, about twice as many calls are required to reduce from 0-197 to 0-99).

If the input range is less than the output (for example, we have a coin flipper and want to roll the dice), multiply (do not add) the Horner algorithm as many times as needed to get a larger input range. The coin spread has a range of 2, CEIL (LN (OUTPUTRANGE) / LN (INPUTRANGE)) is 3, so we need three multiplications:

 for (;;) { x = ( flip() * 2 + flip() ) * 2 + flip(); if (x < 6) { break; } } 

or get a number from 122 to 221 (range = 100) from a roll of dice:

 for (;;) { // ROUNDS = 1 + FLOOR(LN(OUTPUTRANGE)/LN(INPUTRANGE)) and can be hardwired // INPUTRANGE is 6 // x = 0; for (i = 0; i < ROUNDS; i++) { x = 6*x + dice(); } x = dice() + 6 * ( dice() + 6 * ( dice() /* + 6*... */ ) ); if (x < 200) { break; } } // x is now 0..199, x/2 is 0..99 y = 122 + x/2; 
+13
source

Modulo works great with floating point, so what about:

 x = ((x-x_min) % (x_max - x_min) ) + x_min; 

However, it still effectively separates, and you need to configure it for values ​​less than <min ...

You worry about accuracy when the number is far from the range. However, this is not related to the modulo operation; however, it is executed, but is a floating point property. If you take a number from 0 to 1, and you add a large constant to it, say, to bring it into the range from 100 to 101, it will lose some accuracy.

+9
source

Are the minimum and maximum fixed values? If so, you can predefine their range and vice versa:

 const decimal x_min = 5.6m; const decimal x_max = 8.9m; const decimal x_range = x_max - x_min; const decimal x_range_inv = 1 / x_range; public static decimal WrapValue(decimal x) { return x - x_range * floor(x * x_range_inv); } 

Multiplication should be performed slightly better than division.

+1
source
 x = x<x_min? x_min: x>x_max? x_max:x; 

Its a bit confusing, and you can definitely split it into a couple of if statements. But I do not see the need for division to begin with.

Edit:

I don't seem to understand le

 x = x<x_min? x_max - (x_min - x): x>x_max? x_min + (x - x_max):x; 

This will work if your x value doesn't differ much. This may work depending on the use case. Else for a more reliable version. I expect you to need division or repeated (recursive?) Subtraction at least.

This should be a more robust version that continues to perform the above calculation until x becomes stable.

 int x = ?, oldx = x+1; // random init value. while(x != oldx){ oldx = x; x = x<x_min? x_max - (x_min - x): x>x_max? x_min + (x - x_max):x; } 
0
source

How to use the extension method on IComparable .

 public static class LimitExtension { public static T Limit<T>(this T value, T min, T max) where T : IComparable { if (value.CompareTo(min) < 0) return min; if (value.CompareTo(max) > 0) return max; return value; } } 

And unit test:

 public class LimitTest { [Fact] public void Test() { int number = 3; Assert.Equal(3, number.Limit(0, 4)); Assert.Equal(4, number.Limit(4, 6)); Assert.Equal(1, number.Limit(0, 1)); } } 
0
source

LinqPad SAMPLE CODE (Limited to 3 decimal places)

 void Main() { Test(int.MinValue, 0, 1,0.1f, "value = int.MinValue"); Test(int.MinValue, -2,- 1,0.1f, "value = int.MinValue"); Test(int.MaxValue, 0, 1,0.1f, "value = int.MaxValue"); Test(int.MaxValue, -2,- 1,0.1f, "value = int.MaxValue"); Test(-2,-2,-1,0.1f, string.Empty); Test(0,0,1,0.1f, string.Empty); Test(1,1,2,0.1f, string.Empty); Test(int.MinValue, 0, 1, -0.1f, "value = int.MinValue"); Test(int.MinValue, -2,- 1, -0.1f, "value = int.MinValue"); Test(int.MaxValue, 0, 1, -0.1f, "value = int.MaxValue"); Test(int.MaxValue, -2,- 1, -0.1f, "value = int.MaxValue"); Test(-2,-2,-1, -0.1f, string.Empty); Test(0,0,1, -0.1f, string.Empty); Test(1,1,2, -0.1f, string.Empty); } private void Test(float value, float min ,float max, float direction, string comment) { "".Dump(" " + min + " to " + max + " direction = " + direction + " " + comment); for (int i = 0; i < 11; i++) { value = (float)Math.Round(min + ((value - min) % (max - min)), 3); string.Format(" {1} -> value: {0}", value, i).Dump(); value = value + direction < min && direction < 0 ? max + direction : value + direction; } } 

results

 0 to 1 direction = 0.1 value = int.MinValue 0 -> value: 0 1 -> value: 0.1 2 -> value: 0.2 3 -> value: 0.3 4 -> value: 0.4 5 -> value: 0.5 6 -> value: 0.6 7 -> value: 0.7 8 -> value: 0.8 9 -> value: 0.9 10 -> value: 0 -2 to -1 direction = 0.1 value = int.MinValue 0 -> value: -2 1 -> value: -1.9 2 -> value: -1.8 3 -> value: -1.7 4 -> value: -1.6 5 -> value: -1.5 6 -> value: -1.4 7 -> value: -1.3 8 -> value: -1.2 9 -> value: -1.1 10 -> value: -2 0 to 1 direction = 0.1 value = int.MaxValue 0 -> value: 0 1 -> value: 0.1 2 -> value: 0.2 3 -> value: 0.3 4 -> value: 0.4 5 -> value: 0.5 6 -> value: 0.6 7 -> value: 0.7 8 -> value: 0.8 9 -> value: 0.9 10 -> value: 0 -2 to -1 direction = 0.1 value = int.MaxValue 0 -> value: -2 1 -> value: -1.9 2 -> value: -1.8 3 -> value: -1.7 4 -> value: -1.6 5 -> value: -1.5 6 -> value: -1.4 7 -> value: -1.3 8 -> value: -1.2 9 -> value: -1.1 10 -> value: -2 -2 to -1 direction = 0.1 0 -> value: -2 1 -> value: -1.9 2 -> value: -1.8 3 -> value: -1.7 4 -> value: -1.6 5 -> value: -1.5 6 -> value: -1.4 7 -> value: -1.3 8 -> value: -1.2 9 -> value: -1.1 10 -> value: -2 0 to 1 direction = 0.1 0 -> value: 0 1 -> value: 0.1 2 -> value: 0.2 3 -> value: 0.3 4 -> value: 0.4 5 -> value: 0.5 6 -> value: 0.6 7 -> value: 0.7 8 -> value: 0.8 9 -> value: 0.9 10 -> value: 0 1 to 2 direction = 0.1 0 -> value: 1 1 -> value: 1.1 2 -> value: 1.2 3 -> value: 1.3 4 -> value: 1.4 5 -> value: 1.5 6 -> value: 1.6 7 -> value: 1.7 8 -> value: 1.8 9 -> value: 1.9 10 -> value: 1 0 to 1 direction = -0.1 value = int.MinValue 0 -> value: 0 1 -> value: 0.9 2 -> value: 0.8 3 -> value: 0.7 4 -> value: 0.6 5 -> value: 0.5 6 -> value: 0.4 7 -> value: 0.3 8 -> value: 0.2 9 -> value: 0.1 10 -> value: 0 -2 to -1 direction = -0.1 value = int.MinValue 0 -> value: -2 1 -> value: -1.1 2 -> value: -1.2 3 -> value: -1.3 4 -> value: -1.4 5 -> value: -1.5 6 -> value: -1.6 7 -> value: -1.7 8 -> value: -1.8 9 -> value: -1.9 10 -> value: -2 0 to 1 direction = -0.1 value = int.MaxValue 0 -> value: 0 1 -> value: 0.9 2 -> value: 0.8 3 -> value: 0.7 4 -> value: 0.6 5 -> value: 0.5 6 -> value: 0.4 7 -> value: 0.3 8 -> value: 0.2 9 -> value: 0.1 10 -> value: 0 -2 to -1 direction = -0.1 value = int.MaxValue 0 -> value: -2 1 -> value: -1.1 2 -> value: -1.2 3 -> value: -1.3 4 -> value: -1.4 5 -> value: -1.5 6 -> value: -1.6 7 -> value: -1.7 8 -> value: -1.8 9 -> value: -1.9 10 -> value: -2 -2 to -1 direction = -0.1 0 -> value: -2 1 -> value: -1.1 2 -> value: -1.2 3 -> value: -1.3 4 -> value: -1.4 5 -> value: -1.5 6 -> value: -1.6 7 -> value: -1.7 8 -> value: -1.8 9 -> value: -1.9 10 -> value: -2 0 to 1 direction = -0.1 0 -> value: 0 1 -> value: 0.9 2 -> value: 0.8 3 -> value: 0.7 4 -> value: 0.6 5 -> value: 0.5 6 -> value: 0.4 7 -> value: 0.3 8 -> value: 0.2 9 -> value: 0.1 10 -> value: 0 1 to 2 direction = -0.1 0 -> value: 1 1 -> value: 1.9 2 -> value: 1.8 3 -> value: 1.7 4 -> value: 1.6 5 -> value: 1.5 6 -> value: 1.4 7 -> value: 1.3 8 -> value: 1.2 9 -> value: 1.1 10 -> value: 1 
0
source

If you can add a minimum value limit of 0, the LSerni answer simplification is higher: x = ((x % x_max) + x_max) % x_max

The first operation x % x_max will always be negative if x less than 0 min. This allows you to replace the operation of the second module of this simplification with a comparison of less than.

 float wrap0MinValue(float x, float x_max) { int result = toWrap % maxValue; if (result < 0) // set negative result back into positive range result = maxValue + result; return result; } 
0
source

For a very specific case of range 0..1 this works:

 float wrap(float n) { if (n > 1.0) { return n - floor(n); } if (n < 0.0) { return n + ceil(abs(n)); } return n; } 
-1
source

use Wouter de Kort but change

 if (value.CompareTo(max) > 0) return max; 

to

 if (value.CompareTo(max) > 0) return min; 
-2
source

All Articles