【问题标题】:Can I trust a real-to-int conversion of the result of ceil()?我可以相信 ceil() 的结果的实数到整数转换吗?
【发布时间】:2011-11-02 17:26:48
【问题描述】:

假设我有一些代码,例如:

float a, b = ...; // both positive
int s1 = ceil(sqrt(a/b));
int s2 = ceil(sqrt(a/b)) + 0.1;

s1 != s2 有可能吗?我担心a/b 何时是一个完美的正方形。比如a=100.0b=4.0,那么ceil的输出应该是5.00000,但是如果是4.99999呢?

类似的问题:100.0/4.0 是否有可能评估为5.00001,然后ceil 会将其四舍五入为6.00000

我更喜欢在整数数学中执行此操作,但 sqrt 有点搞砸了这个计划。

编辑: 关于如何更好地实现这一点的建议也将不胜感激! ab 值是整数值,所以实际代码更像:ceil(sqrt(float(a)/b))

编辑:根据 levis501 的回答,我想我会这样做:

float a, b = ...; // both positive
int s = sqrt(a/b);
while (s*s*b < a) ++s;

谢谢大家!

【问题讨论】:

  • 这些计算是为了什么?例如,您可以将sqrt(x) &lt; y 更改为x &lt; y * y,这样可以保留整数运算(只要y * y 不会变得太大)。
  • 不幸的是,s1 != s2 很有可能,即使a/b 是一个完美的正方形。浮点运算是出了名的不准确,因此您获得5.00000(或任何您想要的数字)的机会非常渺茫。
  • @peachykeen 即使我们正在处理整数? 5.0 可以完美地表示为浮点数,100.020.0 也可以。
  • @Dan 如果您有一个实现,其中100/4 的计算结果接近于5,那么您肯定有问题。
  • @GMan 我会考虑的...我有a 需要分发到b 盒子的物品。当a&gt;b我想把每个盒子分成4、9、16、25等子盒子。所以s的值是比例(分别为2、3、4、5),或者是盒子沿每个维度的分割数。例如,300 个单元和 20 个盒子需要每个盒子分成 16 个(4x4)。

标签: c++ c floating-point floating-accuracy ceil


【解决方案1】:

我认为这是不可能的。不管sqrt(a/b) 的值是多少,它产生的是我们使用的某个值 N:

int s1 = ceil(N);
int s2 = ceil(N) + 0.1;

由于 ceil 总是产生一个整数值(尽管表示为双精度值),所以我们总是会有一些值 X,第一个产生X.0,第二个产生X.1。转换为int 将始终截断.1,因此两者都会导致X

如果 X 太大以至于 X.1 超出了 double 的范围,似乎会有一个例外。不过,我看不出这在哪里是可能的。除了接近 0(溢出不是问题)外,数字的平方根将始终小于输入数字。因此,在 ceil(N)+0.1 可能溢出之前,在 sqrt(a/b) 中用作输入的 a/b 必须已经溢出。

【讨论】:

  • 我同意我们应该期望信任 ceil,但我找不到 C 标准中的哪个位置(如果有的话)它实际上谈到了 math.h 中函数的准确性。假设 0.5ulp 精度,ceil 必须 返回一个整数,如果实现不能做到这一点,那将是非常不合理的。但是我们知道,说 cos 不能保证准确度为 0.5ulp(或 sqrt,我认为它回答了第二个问题),那么为什么要 ceil 呢?除了因为明显的实现是屏蔽尾数的一些位(如果这些位还不是 0,则添加 1),我的意思是。
  • 我想这取决于我们是否将“计算不小于 x 的最小整数值”理解为“保证返回的值是一个整数,等于 ...”或表示, “精确的数学结果是……,但返回的值通常不准确”。
  • @SteveJessop:实际上,只有 64 位 int 才有可能遇到问题。对于 32 位 int 和带有 53 位尾数的 double 的典型情况,在将 double 转换为精确的整数值有任何困难之前,您就已经使 int 溢出了。
  • 至于为什么我们会期望有所不同,我认为这很简单。使用您的示例 (sqrt),所有非整数结果都是不合理的,因此它们可能无法准确表示。只有当我们用一个完美的正方形呈现它时,才会有任何关于它产生真正准确答案的问题的空间。
  • 好吧,我希望ceil 准确,因为我可以想象一个简单的实现。我所苦恼的只是它是否保证 准确,如果准确的话(C99 和 IEEE 754 是候选者)。同时,我不希望sqrt 一定在 0.5ulp 内准确的原因不是(就像你的情况一样)仅仅因为它不能完全准确。这是因为我碰巧知道在没有高昂成本的情况下很难将其实现到 0.5ulp 精度。也与标准无关,这就是为什么我在那里也不完全满意......
【解决方案2】:

您可能想为您的案例编写一个显式函数。例如:

/* return the smallest positive integer whose square is at least x */
int isqrt(double x) {
  int y1 = ceil(sqrt(x));
  int y2 = y1 - 1;
  if ((y2 * y2) >= x) return y2;
  return y1;
}

这将处理比率a/b 的平方根在double 的精度范围内的奇怪情况。

【讨论】:

  • 我喜欢这种方法。可以证明它是正确的,无需了解或依赖浮点实现细节。
【解决方案3】:

浮点数的相等性确实是一个问题,但恕我直言,如果我们处理整数,则不是。

如果你有100.0/4.0 的情况,它应该完美地评估为25.0,因为25.0 完全可以表示为一个浮点数,与例如相反。 25.1.

【讨论】:

    【解决方案4】:

    是的,s1 != s2 完全有可能。为什么这是一个问题? s1 != (s1 + 0.1) 似乎很自然。

    顺便说一句,如果您希望将5.00001 舍入为5.00000 而不是6.00000,请使用rint 而不是ceil


    并回答实际问题(在您的评论中) - 您可以使用 sqrt 获取起点,然后使用整数算术找到正确的平方。

    int min_dimension_greater_than(int items, int buckets)
    {
        double target = double(items) / buckets;
        int min_square = ceil(target);
        int dim = floor(sqrt(target));
        int square = dim * dim;
        while (square < min_square) {
            seed += 1;
            square = dim * dim;
        }
        return dim;
    }
    

    是的,这可以改进很多,这只是一个速写。

    【讨论】:

    • s1 和 s2 都是整数。演员截断小数
    • 注意 s1, s2 是整数。我会研究 rint,不确定它是否适用于我需要支持的所有平台。
    • @Dan: 如果你没有rint,那么floor(x+0.5f) 会给出同样的结果。
    • @Mike,谢谢。在那种情况下,rint 绝对不是我想要的。我对这个问题的评论解释了我的用例。
    • @levis501 是的,我知道它们都被截断了:我只是不明白你为什么要麻烦添加0.1,如果你希望它被截断。 IOW:要么是无操作,我不知道 OP 为什么要写它,要么不是,我不知道他为什么感到惊讶。
    【解决方案5】:

    s1 将永远等于 s2。

    C 和 C++ 标准并没有说明数学例程的准确性。从字面上看,这个标准是不可能实现的,因为 C 标准说 sqrt(x) 返回 x 的平方根,但是 2 的平方根不能用浮点数精确表示。

    实现始终返回正确舍入结果的具有良好性能的例程(在舍入到最近模式下,这意味着结果是最接近精确结果的可表示浮点数,并解决了有利于低零位)是一个困难的研究问题。好的数学库的目标精度小于 1 ULP(因此返回两个最接近的可表示数字之一),可能略高于 0.5 ULP。 (ULP 是最低精度单位,即指数字段中给定特定值的低位值。)一些数学库可能比这差得多。您必须询问您的供应商或查看文档以获取更多信息。

    所以 sqrt 可能会稍微偏离。如果精确的平方根是一个整数(在整数可以用浮点数精确表示的范围内)并且库保证错误小于 1 ULP,那么 sqrt 的结果必须完全正确,因为除了确切的结果至少距离 1 ULP。

    类似地,如果库保证错误小于 1 ULP,那么 ceil 必须返回准确结果,同样因为准确结果是可表示的,并且任何其他结果至少 1 ULP 之外。此外,ceil 的性质是,我希望任何合理的数学库总是返回一个整数,即使库的其余部分质量不高。

    对于溢出的情况,如果 ceil(x) 超出了所有整数都可以精确表示的范围,那么 ceil(x)+.1 更接近 ceil(x) 而不是任何其他可表示的数字,所以在任何实现浮点标准 (IEEE 754) 的系统中,将 .1 添加到 ceil(x) 的四舍五入结果应该是 ceil(x)。前提是您处于默认舍入模式,即舍入到最近。可以将舍入模式更改为近似无穷大,这可能导致 ceil(x)+.1 为高于 ceil(x) 的整数。

    【讨论】:

      猜你喜欢
      • 2021-12-10
      • 2017-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-16
      • 1970-01-01
      • 2014-08-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多