'Wrap value into range [min,max] without division
Is there any way in C# to wrap a given value x between x_min and x_max. The value should not be clamped as in Math.Min/Max but wrapped like a float modulus.
A way to implement this would be:
x = x - (x_max - x_min) * floor( x / (x_max - x_min));
However, I am wondering if there is an algorithm or C# method that implements the same functionality without divisions and without the likely float-limited-precision issues that may arise when the value lies far away from the desired range.
Solution 1:[1]
Modulo works fine on floating point, so how about:
x = ((x-x_min) % (x_max - x_min) ) + x_min;
It's still effectively a divide though, and you need to tweak it for values less < min...
You are worrying about accuracy when the number is far away from the range. However this is not related to the modulo operation, however it is performed, but is a property of floating point. If you take a number between 0 and 1, and you add a large constant to it, say to bring it into the range 100 to 101, it will lose some precision.
Solution 2:[2]
Are min and max fixed values? If so, you could figure out their range and the inverse of that in advance:
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);
}
The multiplication should perform somewhat better than division.
Solution 3:[3]
How about using an 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 a 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));
}
}
Solution 4:[4]
x = x<x_min? x_min:
x>x_max? x_max:x;
Its a little convoluted, and you can definitely break it into a pair of if statements.. But I don't see the need for division to begin with.
Edit:
I seem to have missunderstood, le
x = x<x_min? x_max - (x_min - x):
x>x_max? x_min + (x - x_max):x;
This would work if your value of x does not vary too much.. which might work depending on the use case. Else for a more robust version I expect you need divide or repeated (recursive?) subtraction atleast.
This should be a more robust version which keeps performing the above calculation until x is 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;
}
Solution 5:[5]
LinqPad SAMPLE CODE (Restricted 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
Solution 6:[6]
If you're able to add the constraint of a min value of 0, simplifying LSerni's answer above is: x = ((x % x_max) + x_max) % x_max
The first x % x_max operation will always be negative when x is less than the 0 min value. This allows the second modulus operation of that simplification to be replaced with a less than 0 comparison.
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;
}
Solution 7:[7]
For the very specific case of range 0..1 this seems to work:
float wrap(float n) {
if (n > 1.0) {
return n - floor(n);
}
if (n < 0.0) {
return n + ceil(abs(n));
}
return n;
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | |
| Solution 3 | Wouter de Kort |
| Solution 4 | |
| Solution 5 | |
| Solution 6 | ShawnFeatherly |
| Solution 7 | Andreas Lund |
