C# 将浮点转换为UInt32-哪个表达式更精确

C# 将浮点转换为UInt32-哪个表达式更精确,c#,floating-point,floating-point-precision,numerical-stability,C#,Floating Point,Floating Point Precision,Numerical Stability,我有一个数字float x,它应该在范围内,但它经历了几个数字运算-结果可能稍微超出范围 我需要使用整个UInt32范围将此结果转换为uint y。当然,我需要在范围内夹紧x,并对其进行缩放 但哪种操作顺序更好 y = (uint)round(min(max(x, 0.0F), 1.0F) * UInt32.MaxValue) 或 换言之,最好先缩放,然后夹紧,还是先夹紧再缩放?我对IEEE浮点表示法不是很了解,但我相信上述表达式的计算顺序有所不同。鉴于x可能稍微超出[0,1]的范围,第二种方

我有一个数字
float x
,它应该在范围内,但它经历了几个数字运算-结果可能稍微超出范围

我需要使用整个
UInt32
范围将此结果转换为
uint y
。当然,我需要在范围内夹紧
x
,并对其进行缩放

但哪种操作顺序更好

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


换言之,最好先缩放,然后夹紧,还是先夹紧再缩放?我对IEEE浮点表示法不是很了解,但我相信上述表达式的计算顺序有所不同。

鉴于
x
可能稍微超出
[0,1]
的范围,第二种方法不如第一种方法容易,因为UInt32值空间中存在钳制问题,即UInt32中的每个数字都有效。第一种方法也更容易理解

即:


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

Single无法支持足够的精度来维持中间结果,因此需要先缩放然后钳制,但不能钳制到UInt32.MaxValue,因为它不能用Single表示。您可以安全夹紧的最大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;
        }

因为从[0.0f..1.0f]到[0..UInt32.MaxValue]的乘法本身可以是近似的,所以最明显具有所需特性的运算顺序是乘法,然后钳制,然后舍入

要钳制的最大值是紧靠232下方的浮动,即4294967040.0f。尽管此数字比UInt32.MaxValue低几个单位,但允许任何较大的值都意味着转换到
UInt32
时溢出

以下任何一行都可以工作:

y = (uint)round(min(max(x * 4294967040.0F, 0.0F), 4294967040.0F))
在第一个版本中,您可以选择乘以
UInt32.MaxValue
。选择是在总体结果稍大的情况下(并因此将接近1.0f但低于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)

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

  • 黑色必须映射为黑色,白色必须映射为白色(在本例中,表示0.0->0和1.0->2^32-1)
  • 源格式中映射到目标格式中每个值的间隔的宽度必须尽可能相等
  • 等间距输入应映射到目标格式中尽可能等间距的输出
第二点的推论是,使用round的颜色格式转换几乎总是不正确的,因为映射到最小和最大结果的容器通常太小,减半。对于像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);
如果(x=1.0F)y=UInt32.MaxValue;
else y=(uint)数学截断(x*4294967296.0F);

@Duncan我认为这个问题不是特定于语言的,因为Java和C都使用IEEE浮点表示法来表示浮点数。这可能有助于投票人留下评论。这个问题对我来说似乎很有道理。也许是错误的标签鼓励了一些人投了反对票……好吧,我留下了C#tag。。。这个问题应该更有效。Java没有
uint
。。(或任何其他未签名的数据类型)溢出的处理方式不同。。Int32溢出变为负数,超出了您的时间间隔,UInt32溢出变为时间间隔内的数字。您所说的“不那么容易”是什么意思?这是一个关于浮点精度的问题,而不是易于读取的问题。由于整个值空间对UInt32有效,而不是浮点/单精度中的[0,1]限制,因此执行箝位比较麻烦。如果先箝位,然后乘法,然后舍入,则乘法会使值返回超出范围。实际上,
UInt32.MaxValue
不可表示为浮点,在转换为浮点时向上舍入,因此
1.0F*UInt32.MaxValue
是一个浮点,表示大于
UInt32.MaxValue
的值。最明显正确的方法似乎是乘法,然后钳位,然后转换为
UInt32
@PascalCuoq。你是对的
1.0f*UInt32.MaxValue
等于
UInt32.MaxValue+1
,表达式
(uint)(1.0f*UInt32.MaxValue)
溢出到
0
。你能给我一个答案让我接受吗?如果需要,我将编辑答案并添加详细信息,只是想给你评分。@PascalCuoq很有趣,
Math.Min(UInt32.MaxValue+1.0f,UInt32.MaxValue)
仍然解析为
UInt32.MaxValue+1.0f
。我无法使用
double
s(出于硬件原因),因此到
uint
的最终转换似乎是通过分别处理特殊情况
x==1.0f
。感谢您的测试。然而,正如PascalCuoq所指出的,第二种方法不适用于
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)
if (x < 0.5f)
{
  if (x <= 0.0f) y = ...
  else y = ...
}
else
{
  if (x >= 1.0f) y = ...
  else y = ...
}
convert_uint_sat(x * 0x1.0p32f)
if (x <= 0.0F) y = UInt32.MinValue;
else if (x >= 1.0F) y = UInt32.MaxValue;
else y = (uint)Math.Truncate(x * 4294967296.0F);