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)
- 源格式中映射到目标格式中每个值的间隔的宽度必须尽可能相等
- 等间距输入应映射到目标格式中尽可能等间距的输出
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);