将值包含在范围[min,max]中,不分割

时间:2013-01-19 15:19:42

标签: c# c#-4.0

在C#中是否有任何方法可以在x_min和x_max之间包装给定值x。该值不应像Math.Min/Max那样被钳制,而是像float模数一样包裹。

实现这一目标的方法是:

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

但是,我想知道是否有一个算法或C#方法在没有分割的情况下实现相同的功能,并且没有可能在值远离所需范围时出现的浮点限制精度问题。

9 个答案:

答案 0 :(得分:11)

你可以使用两个模运算来包装它,仍然相当于一个分区。我不认为在没有假设x的情况下有更有效的方法。

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

公式中的附加和和模数用于处理x实际上小于x_min并且模数可能为负的情况。或者您可以使用if和单个模块化部门执行此操作:

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);

除非xx_minx_max不远,并且只需很少的总和或减法即可到达(也可以考虑错误传播),我认为模数是你唯一可用的方法。

没有分区

请记住,错误传播可能会变得相关,我们可以通过循环执行此操作:

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

关于概率的注释

模运算的使用具有一些统计意义(浮点运算也会有不同的算法)。

例如,我们将包括在0和5之间的随机值(例如六面骰子结果)包装到[0,1]范围(即硬币翻转)中。然后

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

如果输入具有平坦的频谱,即每个数字(0-5)具有1/6概率,则输出也将是平坦的,并且每个项目将具有3/6 = 50%的概率。

但是如果我们有一个五面骰子(0-4),或者如果我们有一个介于0和32767之间的随机数并希望在(0,09)范围内减少它以获得一个百分比,那么输出将是不平坦,有些数字会比其他数字略微(或不那么轻微)。在五面骰子到硬币翻盖的情况下,头部与尾部的比例为60%-40%。在32767%至百分比的情况下,低于67的百分比将是CEIL(32767/100)/ FLOOR(32767/100)=比其他人更高的0.3%。

因此,如果想要平坦输出,则必须确保(max-min)是输入范围的除数。在32767和100的情况下,输入范围必须在最接近的百位(减一)32699被截断,因此(0-32699)包含32700个结果。每当输入为> = 32700时,必须再次调用输入函数以获得新值:

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

当(INPUTRANGE%OUTPUTRANGE)/(INPUTRANGE)显着时,开销可能相当大(例如,减少0-197到0-99需要大约两倍的呼叫)。

如果输入范围小于输出范围(例如我们有一个硬币鳍状肢,我们想制作一个骰子),使用Horner算法乘以(不要添加)多次,以获得输入范围更大硬币翻转范围为2,CEIL(LN(OUTPUTRANGE)/ LN(INPUTRANGE))为3,因此我们需要三次乘法:

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

或者从骰子中得到122到221之间的数字(范围= 100):

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;

答案 1 :(得分:8)

Modulo在浮点上工作正常,那么如何:

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

它仍然实际上是一个鸿沟,你需要调整它以减少值&lt;分钟...

当数字远离范围时,您会担心准确性。然而,这与模运算无关,但是它被执行,但是是浮点的属性。如果你取一个介于0和1之间的数字,然后你给它添加一个大常数,比如把它带到100到101的范围内,就会失去一些精度。

答案 2 :(得分:1)

最小和最大固定值?如果是这样,你可以提前计算出它们的范围和反之:

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);
}

乘法应该比分割好一些。

答案 3 :(得分:0)

x = x<x_min?  x_min:
    x>x_max?  x_max:x;

它有点令人费解,你绝对可以把它分成一对if语句。但我不认为需要分裂开始。

编辑:

我似乎很想念,le

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

如果x的值变化不大,这将起作用..这可能会起作用,具体取决于用例。另外对于更强大的版本,我希望你至少需要除法或重复(递归?)减法。

这应该是一个更强大的版本,在x稳定之前一直执行上述计算。

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;
}

答案 4 :(得分:0)

如何在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;
    }
}

单元测试:

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));

    }
}

答案 5 :(得分:0)

LinqPad SAMPLE CODE(限制为3位小数)

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;
    }
} 

结果

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

答案 6 :(得分:0)

如果您能够添加最小值为0的约束,则简化LSerni上面的答案是:x = ((x % x_max) + x_max) % x_max

x % x_max小于0分钟值时,第一个x操作将始终为负数。这允许将该简化的第二模数运算替换为小于0的比较。

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;
}

答案 7 :(得分:-1)

对于范围为0..1的非常特殊的情况,这似乎可行:

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

答案 8 :(得分:-2)

使用Wouter de Kort的答案,但更改

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

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