4

float x<0,1> の範囲内にあるはずの数値がありますが、いくつかの数値演算が行われます。結果は <0,1> からわずかに外れている可能性があります。

uint yこの結果を の全範囲を使用するように変換する必要がありUInt32ます。もちろん、x<0,1> の範囲でクランプしてスケーリングする必要があります。

しかし、操作のどの順序が優れているのでしょうか?

y = (uint)round(min(max(x, 0.0F), 1.0F) * UInt32.MaxValue)

また

y = (uint)round(min(max(x * UInt32.MaxValue, 0.0F), UInt32.MaxValue)

つまり、最初にスケーリングしてから、クランプまたはクランプしてからスケーリングする方が良いですか? 私は IEEE の浮動小数点表現にあまり詳しくありませんが、上記の式の計算順序に違いがあると思います。

4

4 に答える 4

3

[0.0f .. 1.0f] から [0 .. UInt32.MaxValue] への乗算自体が概算になる可能性があるため、必要なプロパティを最も明確に持つ操作の順序は、乗算、クランプ、丸めの順です。

クランプする最大値は、2 32のすぐ下の浮動小数点数、つまり です4294967040.0f。この数値は UInt32.MaxValue を数単位下回っていますが、それ以上の値を許可すると、 への変換がオーバーフローすることになりUInt32ます。

以下の行のいずれかが機能するはずです。

y = (uint)round(min(max(x * 4294967040.0F, 0.0F), 4294967040.0F))

この最初のバージョンでは、UInt32.MaxValue代わりに乗算するオプションがあります。選択肢は、全体的に非常にわずかに大きい結果を得る (したがって、1.0f に近いがそれより低いいくつかの値を 4294967040 に丸める) か、1.0f 以上の値のみを 4294967040 に送信するかです。


[0.0f .. 1.0f] に固定することもできます。後で大きすぎる数値を掛けない場合は、変換できる最大の浮動小数点数よりも値が大きくなるリスクがありません。

y = (uint)round(min(max(x, 0.0F), 1.0F) * 4294967040.0F)

までのコンバージョンを作成することについて、以下のコメントへの提案UInt32.MaxValue:

if (x <= 0.0f) y = 0
else if (x < 0.5f) y = (uint) round (x * 4294967296.0F)
else if (x >= 1.0f) y = UInt32.MaxValue
else y = UInt32.MaxValue - (uint) round ((1.0f - x) * 4294967296.0F)

xからまでの関数と見なされるこの計算yは増加しており (約 0.5f を含む)、 まで上がりUInt32.MaxValueます。値の分布が最も可能性が高いと思われるものに従って、テストを並べ替えることができます。特に、実際に 0.0f 未満または 1.0f を超える値はほとんどないと仮定すると、最初に 0.5f と比較してから、関連する範囲のみを比較できます。

if (x < 0.5f)
{
  if (x <= 0.0f) y = ...
  else y = ...
}
else
{
  if (x >= 1.0f) y = ...
  else y = ...
}
于 2014-06-23T09:23:59.040 に答える
0

Single は中間結果を維持するのに十分な精度をサポートできないため、スケーリングしてからクランプする必要がありますが、UInt32.MaxValue にクランプすることはできません。安全にクランプできる最大 UInt32 は4294967167 です

このコードから

        Single maxUInt32 = (Single)UInt32.MaxValue;
        Double accurateValue = maxUInt32;
        while (true)
        {
            accurateValue -= 1;
            Single temp = (Single)accurateValue;
            Double temp2 = (Double)temp;
            if (temp2 < (Double)UInt32.MaxValue)
                break;
        }

このテストを見て...

        Double val1 = UInt32.MaxValue;
        Double val2 = val1 + 1;

        Double valR = val2 / val1;

        Single sValR = (Single)valR;

        //Straight Scale and Cast
        UInt32 NewValue = (UInt32)(sValR * UInt32.MaxValue);
        //Result = 0;

        //Clamp Then Scale Then Cast
        UInt32 NewValue2 = (UInt32)(Math.Min(sValR, 1.0f) * UInt32.MaxValue);
        //Result = 0;

        //Scale Then Clamp Then Cast
        UInt32 NewValue3 = (UInt32)(Math.Min(sValR * UInt32.MaxValue, UInt32.MaxValue));
        //Result = 0;

        //Using Doubles
        //Straight Scale and Cast
        UInt32 NewValue4 = (UInt32)(valR * UInt32.MaxValue);
        //Result = 0;

        //Clamp Then Scale Then Cast
        UInt32 NewValue5 = (UInt32)(Math.Min(valR, 1.0f) * UInt32.MaxValue);
        //Result = 4294967295;

        //Scale Then Clamp Then Cast
        UInt32 NewValue6 = (UInt32)(Math.Min(valR * UInt32.MaxValue, UInt32.MaxValue));
        //Result = 4294967295;

        //Comparing to 4294967167
        //Straight Scale and Cast
        UInt32 NewValue7 = (UInt32)(sValR * UInt32.MaxValue);
        //Result = 0;

        //Clamp Then Scale Then Cast
        UInt32 NewValue8 = (UInt32)(Math.Min(sValR, 1.0f) * UInt32.MaxValue);
        //Result = 0;

        //Scale Then Clamp Then Cast
        UInt32 NewValue9 = (UInt32)(Math.Min(sValR * UInt32.MaxValue, 4294967167));
        //Result = 4294967040;
于 2014-06-23T07:48:16.383 に答える
0

UInt32 値空間のクランプの問題、つまり UInt32 のすべての数値が有効であるため、2 番目のアプローチは最初のアプローチほど簡単ではありませんx[0,1]最初のものも理解しやすいです。つまり、間隔とスケールで数値を取得します。

すなわち:

var y = (UInt32) (Math.Min(Math.Max(x, 0f), 1f) * UInt32.MaxValue);

また、数百万の値でテストしましたが、同じ結果が得られました。どちらを使用しても構いません。

于 2014-06-23T07:28:37.957 に答える