13

C#で、指定された値xをx_minとx_maxの間にラップする方法はありますか?値は、のようにクランプするのではなく、モジュラスMath.Min/Maxのようにラップする必要があります。float

これを実装する方法は次のとおりです。

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

ただし、分割せずに、値が目的の範囲から遠く離れている場合に発生する可能性のあるフロート制限精度の問題がない、同じ機能を実装するアルゴリズムまたはC#メソッドがあるかどうか疑問に思っています。

4

9 に答える 9

20

2つのモジュロ演算を使用してラップできますが、これは除算と同等です。について何かを仮定せずにこれを行うより効率的な方法はないと思います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_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 += d;
}
while (x > x_max) {
    x -= d;
}

確率に関する注記

モジュラー演算の使用には、いくつかの統計的意味があります(浮動小数点演算も異なるものになります)。

たとえば、含まれる0から5までのランダムな値(たとえば、6面のサイコロの結果)を[0,1]の範囲(つまり、コイントス)にラップするとします。それで

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

入力のスペクトルがフラットである場合、つまり、すべての数値(0-5)の確率が1/6の場合、出力もフラットになり、各項目の確率は3/6 = 50%になります。

ただし、5面のサイコロ(0〜4)がある場合、または0〜32767の乱数があり、パーセンテージを得るために(0、99)の範囲でそれを減らしたい場合、出力はフラットになりません。 、および一部の数値は、他の数値よりもわずかに(またはそれほどわずかではない)可能性が高くなります。5面のサイコロからコイントスの場合、頭と尻尾は60%〜40%になります。32767からパーセントの場合、67未満のパーセンテージは、CEIL(32767/100)/ FLOOR(32767/100)=他よりも0.3%高くなる可能性があります。

(これをより明確に確認するには、番号を「00000」から「32767」までと考えてください。328がスローされるたびに、番号の最初の3桁は「327」になります。これが発生すると、最後の2桁のみが移動できます。 「00」から「67」までは、32768が範囲外であるため、「68」から「99」にすることはできません。したがって、00から67までの数字の可能性がわずかに高くなります。

したがって、フラットな出力が必要な場合は、(max-min)が入力範囲の約数であることを確認する必要があります。32767および100の場合、入力範囲は最も近い100(マイナス1)の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に減らすには、約2倍の呼び出しを行う必要があります)。

入力範囲が出力範囲よりも小さい場合(たとえば、コインフリッパーがあり、サイコロを投げたい場合)、ホーナーのアルゴリズムを必要な回数だけ乗算(追加しないでください)して、より大きな入力範囲を取得します。コイントスの範囲は2で、CEIL(LN(OUTPUTRANGE)/ LN(INPUTRANGE))は3なので、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;
于 2013-01-19T16:04:44.217 に答える
9

Moduloは浮動小数点で正常に動作するので、次のようにします。

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

それでも事実上分割であり、<分未満の値になるように微調整する必要があります...

数値が範囲から遠く離れている場合、精度が心配になります。ただし、これはモジュロ演算とは関係ありませんが、実行されますが、浮動小数点のプロパティです。0から1までの数値を取り、それに大きな定数を追加すると、たとえば100から101の範囲にすると、ある程度の精度が失われます。

于 2013-01-19T15:27:34.177 に答える
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);
}

乗算は除算よりもいくらか優れたパフォーマンスを発揮するはずです。

于 2013-01-19T16:08:44.937 に答える
0
x = x<x_min?  x_min:
    x>x_max?  x_max:x;

少し複雑で、間違いなくifステートメントのペアに分割できます。しかし、そもそも除算の必要性はわかりません。

編集:

私は誤解しているようです、ル

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;
}
于 2013-01-19T15:22:49.553 に答える
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));

    }
}
于 2013-01-19T15:28:35.773 に答える
0

LinqPadサンプルコード(小数点以下第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
于 2015-03-01T09:44:35.987 に答える
0

最小値0の制約を追加できる場合、上記のLSerniの答えを単純化すると次のようになります。x = ((x % x_max) + x_max) % x_max

が0分の値未満の場合、最初のx % x_max操作は常に負になります。xこれにより、その単純化の2番目のモジュラス演算を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;
}
于 2017-07-25T04:28:58.070 に答える
-2

範囲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;
}
于 2019-07-25T13:33:35.603 に答える
-3

Wouter de Kortの答えを使用しますが、変更します

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

if (value.CompareTo(max) > 0) return min;
于 2013-01-19T16:02:09.490 に答える