将float转换为UInt32 - 哪个表达式更精确

时间:2014-06-23 07:14:54

标签: c# floating-point floating-point-precision numerical-stability

我的号码float x应该是< 0,1>范围但它经历了几次数值运算 - 结果可能略微超出< 0,1&gt ;.

我需要使用uint y的整个范围将此结果转换为UInt32。当然,我需要在< 0,1>中钳制x。范围和规模。

但是哪种操作顺序更好?

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

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

换句话说,最好先缩放,然后钳制OR钳位然后缩放?我在IEEE浮点表示中不是很深刻,但我相信上述表达式的计算顺序存在差异。

4 个答案:

答案 0 :(得分: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但低于它的几个值)或者仅向1.09及以上的值发送到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)

此计算被视为从xy的函数正在增加(包括大约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 = ...
}

答案 1 :(得分:2)

正确的颜色格式转换的三个基本属性是:

  • 黑色必须映射到黑色和白色必须映射到白色(在这种情况下意味着0.0 - > 0和1.0 - > 2 ^ 32-1)
  • 源格式中映射到目标格式中每个值的间隔必须具有尽可能相等的宽度。
  • 均匀间隔的输入应映射到目标格式中尽可能均匀间隔的输出。

第二点的必然结果是使用round的颜色格式转换几乎总是不正确的,因为映射到最小和最大结果的bin通常太小了一半。对于像uint32这样的高精度格式来说,这并不是那么重要,但是要做到这一点仍然很好。

您在评论中提到您的C#代码正在转换为OpenCL。到目前为止,OpenCL是我遇到的任何语言的最佳转换(严重的是,如果你正在设计一种面向计算的语言并且你没有复制OpenCL在这里所做的那些,你做错了),这使得这很简单:

convert_uint_sat(x * 0x1.0p32f)

但是,你的问题实际上是关于C#;我不是C#程序员,但那里的方法应该是这样的:

if (x <= 0.0F) y = UInt32.MinValue;
else if (x >= 1.0F) y = UInt32.MaxValue;
else y = (uint)Math.Truncate(x * 4294967296.0F);

答案 2 :(得分:0)

鉴于x可能略微超出[0,1],由于UInt32值空间中的钳位问题,第二种方法并不像第一种方法那么容易,即UInt32中的每个数字都是有效的。第一个也更容易理解,即以间隔和比例获得数字。

即:

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

另外,我用几百万个值测试了它们,它们给出了相同的结果。使用哪一个并不重要。

答案 3 :(得分: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;