将 `std::floor()` 和 `std::ceil()` 转换为整数类型是不是总是给出正确的结果?
Posted
技术标签:
【中文标题】将 `std::floor()` 和 `std::ceil()` 转换为整数类型是不是总是给出正确的结果?【英文标题】:Does casting `std::floor()` and `std::ceil()` to integer type always give the correct result?将 `std::floor()` 和 `std::ceil()` 转换为整数类型是否总是给出正确的结果? 【发布时间】:2016-05-02 00:30:42 【问题描述】:我很怀疑这些函数之一可能会给出这样的错误结果:
std::floor(2000.0 / 1000.0) --> std::floor(1.999999999999) --> 1
or
std::ceil(18 / 3) --> std::ceil(6.000000000001) --> 7
这样的事情会发生吗?如果确实存在这样的风险,我打算使用下面的功能来安全地工作。但是,这真的有必要吗?
constexpr long double EPSILON = 1e-10;
intmax_t GuaranteedFloor(const long double & Number)
if (Number > 0)
return static_cast<intmax_t>(std::floor(Number) + EPSILON);
else
return static_cast<intmax_t>(std::floor(Number) - EPSILON);
intmax_t GuaranteedCeil(const long double & Number)
if (Number > 0)
return static_cast<intmax_t>(std::ceil(Number) + EPSILON);
else
return static_cast<intmax_t>(std::ceil(Number) - EPSILON);
(注意:我假设给定的“long double”参数将适合“intmax_t”返回类型。)
【问题讨论】:
我认为这些例子是安全的(整数,在浮点精度范围内),但是,例如,“3.3 / 1.1”可能会给出“不精确的 3”。EPSILON
不会救你。它是在 1.0 处的最小显着差异量,即您可以添加到 1.0 以获得不同值的最小值。如果您的结果可能大于或小于 1.0,您将需要不同的 EPSILON
。如果您认为您需要 EPSILON
来解决任何问题,那么您很可能会在您的软件中引入一个非常微妙的错误。
您可以考虑使用std::lround
,而不是static_cast
,它返回最接近的int:int i = std::lround(std::ceil(f))
。这将消除对 epsilon 或条件的需要。
【参考方案1】:
人们通常认为浮点运算产生的结果带有小的、不可预测的、准随机错误。此印象不正确。
浮点算术计算尽可能精确。 18/3
将始终产生正好 6。 1/3
的结果不会正好是三分之一,但它会是最接近三分之一的可表示为浮点数的数字。
因此,您展示的示例保证始终有效。至于您建议的“保证地板/天花板”,这不是一个好主意。某些操作序列可以很容易地将错误吹到远远高于1e-10
,而某些其他用例将要求1e-10
被正确识别(和上限)为非零。
根据经验,硬编码的 epsilon 值是代码中的错误。
【讨论】:
【参考方案2】:只要浮点值 x 和 y 准确表示您使用的类型范围内的整数,就没有问题 --x / y
将始终产生一个准确表示的浮点值整数结果。你正在做的转换为 int 总是有效的。
但是,一旦浮点值超出类型 (Representing integers in doubles) 的整数可表示范围,epsilons 就没有帮助。
考虑这个例子。 16777217 是不能精确表示为 32 位 float
的最小整数:
int ix=16777217, iy=97;
printf("%d / %d = %d", ix, iy, ix/iy);
// yields "16777217 / 97 = 172961" which is accurate
float x=ix, y=iy;
printf("%f / %f = %f", x, y, x/y);
// yields "16777216.000000 / 97.000000 = 172960.989691"
在这种情况下,错误为负;在其他情况下(尝试 16777219 / 1549),错误是肯定的。
虽然添加 epsilon 以使 floor
工作很诱人,但它不会大大提高准确性。 当值相差更多数量级时,误差会大于 1,无法保证整数精度。 具体而言,当x/y
超过最大值时。可以表示,误差可以超过1.0,所以epsilon是没有帮助的。
如果这开始发挥作用,您将不得不考虑改变您的数学方法——运算顺序、使用对数等。
【讨论】:
【参考方案3】:使用双打时可能会出现这样的结果。您可以使用 round 或减去 0.5,然后使用 std::ceil 函数。
【讨论】:
问题是关于使用可表示为整数的操作数和结果的计算。【参考方案4】:在您列出的具体示例中,我认为这些错误永远不会发生。
std::floor(2000.0 /*Exactly Representable in 32-bit or 64-bit Floating Point Numbers*/ / 1000.0 /*Also exactly representable*/) --> std::floor(2.0 /*Exactly Representable*/) --> 2
std::ceil(18 / 3 /*both treated as ints, might not even compile if ceil isn't properly overloaded....?*/) --> 6
std::ceil(18.0 /*Exactly Representable*/ / 3.0 /*Exactly Representable*/) --> 6
话虽如此,如果您的数学运算依赖于这些函数对于浮点数的行为完全正确,那可能会说明您需要重新考虑/重新检查的设计缺陷。
【讨论】:
以上是关于将 `std::floor()` 和 `std::ceil()` 转换为整数类型是不是总是给出正确的结果?的主要内容,如果未能解决你的问题,请参考以下文章
具有相同值但不同类型的参数的 std::floor 函数的不同值
如何将CString和:: std :: string :: std :: wstring互相转换?
c++ 如何将 std::mutex 和 std::lock_guard 与仿函数一起使用?
C++ 将预先保留的哈希映射(std::unordered_map)与整数键和连续数据数组(std::vector)进行比较