我的号码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浮点表示中不是很深刻,但我相信上述表达式的计算顺序存在差异。
答案 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)
此计算被视为从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 = ...
}
答案 1 :(得分:2)
正确的颜色格式转换的三个基本属性是:
第二点的必然结果是使用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;