在 C# 中是否有任何方法可以将给定值 x 包装在 x_min 和 x_max 之间。该值不应像 Math.Min/Max
中那样被限制,而是像 float
模数一样包裹。
实现这个的方法是:
x = x - (x_max - x_min) * floor( x / (x_max - x_min));
但是,我想知道是否有一种算法或 C# 方法可以在不除法的情况下实现相同的功能,并且不会出现当值远离所需范围时可能出现的浮点精度受限问题。
最佳答案
您可以使用两个模运算将其包装起来,这仍然等同于除法。如果不对 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 += d;
}
while (x > x_max) {
x -= d;
}
关于概率的注释
模运算的使用有一些统计意义(浮点运算也有不同的意义)。
例如,假设我们将 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, 99) 范围内以获得百分比,输出将不是平坦的,有些数字会比其他数字稍微(或不是那么小)更有可能。在五面骰子到掷硬币的情况下,正面与反面的概率为 60%-40%。在 32767 百分比的情况下,低于 67 的百分比将是 CEIL(32767/100)/FLOOR(32767/100) = 0.3%,比其他百分比出现的可能性高。
(为了更清楚地看到这一点,考虑从“00000”到“32767”的数字:每 328 次掷一次,数字的前三位数字将是“327”。发生这种情况时,最后两位数字只能从“00”到“67”,它们不能从“68”到“99”,因为 32768 超出范围。因此,从 00 到 67 的数字更有可能。
因此,如果想要平坦的输出,就必须确保 (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
}
}
#endif
return x_min + (
(
(x - x_min) % d
) + d
) % d;
当 (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;
关于c# - 将值包装到 [min,max] 范围内而不除法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14415753/