根据常识,什么是正确的: (int) blabla * 255.99999999999997 还是 round(blabla*255)?

问题描述 投票:0回答:3

最近我在 webkit 源码中发现了这个有趣的东西,与颜色转换(hsl 到 rgb)相关:

http://osxr.org/android/source/external/webkit/Source/WebCore/platform/graphics/Color.cpp#0111

const double scaleFactor = nextafter(256.0, 0.0); // it's here something like 255.99999999999997
// .. some code skipped
return makeRGBA(static_cast<int>(calcSomethingFrom0To1(blablabla) * scaleFactor), 

我在这里找到的相同:http://www.filewatcher.com/p/kdegraphics-4.6.0.tar.bz2.5101406/kdegraphics-4.6.0/kolourpaint/imagelib/effects/kpEffectHSV.cpp.html

(int)(value * 255.999999)

使用这种技术到底正确吗?为什么不直接使用像 round(blabla * 255) 这样的东西? 是C/C++的特性吗?严格来说,在 100 种情况下,有 27 种情况会返回并不总是正确的结果。请参阅电子表格 https://docs.google.com/spreadsheets/d/1AbGnRgSp_5FCKAeNrELPJ5j9zON9HLiHoHC870PwdMc/edit?usp=sharing

请有人解释一下——我认为这应该是一些基本的东西。

c++ c casting rounding floor
3个回答
27
投票

简短回答:

(int)(blabla * 255.99999999999997)
应该是首选。

解释如下。

通常我们希望将(闭)区间

x
中的实值
[0,1]
映射到
j
范围内的整数值
[0 ...255]

我们希望以“公平”的方式做到这一点,这样,如果实数在范围内均匀分布,则离散值将近似等概率:256 个离散值中的每一个都应该获得“相同的份额”(1 /256) 来自

[0,1]
区间。也就是说,我们想要这样的映射:

[0    , 1/256) -> 0
[1/256, 2/256) -> 1 
...
[254/256, 255/256) -> 254
[255/256, 1]       -> 255

我们不太关心过渡点 [*],但我们确实希望覆盖整个范围 [0,1]。如何实现?

如果我们简单地做

j = (int)(x *255)
:值255几乎永远不会出现(仅当
x=1
时);其余值
0...254
将分别获得间隔的 1/255。 无论在极限点的舍入行为如何,这都是不公平的。

如果我们这样做

j = (int)(x * 256)
:这个分区将是公平的,除了一个单一问题:当
x=1
[**]

时,我们会得到值 256(超出范围!)

这就是为什么

j = (int)(x * 255.9999...)
(其中
255.9999...
实际上是小于 256 的最大双精度)。

另一种实现(也是合理的,几乎等效)是

j = (int)(x * 256); 
if(j == 256)  j = 255;  
// j = x == 1.0 ? 255 : (int)(x * 256); // alternative

但这会更笨拙并且可能效率更低。

round()
在这里没有帮助。例如,
j = (int)round(x * 255)
会将 1/255 分配给整数
j=1...254
,并将该值的一半分配给极值点
j=0
j=255

enter image description here

[*] 我的意思是:我们对“小”邻域(例如 3/256)中发生的情况并不是非常感兴趣:四舍五入可能会得到 2 或 3,这并不重要。但我们对极值感兴趣:我们想要分别为

x=0
x=1
获得 0 和 255。

[**] IEEE 浮点标准保证这里没有舍入歧义:整数承认精确的浮点表示,乘积将是精确的,并且转换将始终给出 256。此外,我们保证

1.0 * z = z


7
投票

总的来说,我认为

(int)(blabla * 255.99999999999997)
比使用
round()
更正确。

为什么?

因为使用

round()
,0 和 255 的范围只有 1-254 的“一半”。如果您
round()
,则 0-0.00196078431 会映射到 0,而 0.00196078431-0.00588235293 会映射到 1。这意味着 1 出现的概率比 0 高 200%,严格来说,这是一种不公平的偏差。

相反,如果 1 乘以 255.99999999999997,然后再乘以底数(这就是转换为整数所做的事情,因为它会被截断),那么从 0 到 255 的每个整数都有相同的可能性。

如果您的电子表格以小数百分比计数(即每次以 0.01% 而不是 1% 计数),则可能会更好地显示这一点。 我制作了一个简单的电子表格来展示这一点。如果您查看该电子表格,您会发现 0 在

round()
时存在不公平的偏差,但使用其他方法时,情况是公平且平等的。


5
投票

转换为 int 与向下取整函数具有相同的效果(即截断)。当你调用 round 时,会四舍五入到最接近的整数。

他们做不同的事情,所以选择你需要的。

© www.soinside.com 2019 - 2024. All rights reserved.