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

我有一个float x ,它应该在范围内,但它经过几次数值运算 – 结果可能略微超出。

我需要使用整个UInt32范围将此结果转换为uint y 。 当然,我需要在范围内钳制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浮点表示中不是很深刻,但我相信上述表达式的计算顺序有所不同。

因为从[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.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 = ... } 

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

  • 黑色必须映射到黑色和白色必须映射到白色(在这种情况下意味着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); 

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

即:

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

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

单个不能支持足够的精度来维持中间结果,所以你需要缩放然后钳制,但你不能钳位到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;