在C#中是否有任何方法可以在x_min和x_max之间包装给定值x。该值不应像Math.Min/Max
那样被钳制,而是像float
模数一样包裹。
实现这一目标的方法是:
x = x - (x_max - x_min) * floor( x / (x_max - x_min));
但是,我想知道是否有一个算法或C#方法在没有分割的情况下实现相同的功能,并且没有可能在值远离所需范围时出现的浮点限制精度问题。
答案 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);
除非x
离x_min
和x_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;