在C中实现ceil()

时间:2012-09-05 10:57:12

标签: c floating-point implementation ceil

我想在ceil()中实现自己的C。通过图书馆搜索源代码和发现here,但似乎很难理解。我要干净&优雅的代码。

我也搜索了SO,找到了一些答案here。没有一个答案似乎是正确的。其中一个答案是:

#define CEILING_POS(X) ((X-(int)(X)) > 0 ? (int)(X+1) : (int)(X))
#define CEILING_NEG(X) ((X-(int)(X)) < 0 ? (int)(X-1) : (int)(X))
#define CEILING(X) ( ((X) > 0) ? CEILING_POS(X) : CEILING_NEG(X) )

AFAIK,ceil()的返回类型不是int。宏在这里是否类型安全? 此外,上述实施是否适用于负数?

实施它的最佳方式是什么?

你能提供干净的代码吗?

4 个答案:

答案 0 :(得分:2)

对于大于INT_MAX但仍然可以完全表示为double的数字,您引用的宏肯定无法正常工作。

正确实现ceil()的唯一方法(假设你不能使用等效的汇编指令实现它)是对浮点数的二进制表示进行比特纠缠,就像在{ {1}}第一个链接后面的源文件。理解代码如何工作需要理解底层平台的浮点表示 - 表示很可能是IEEE 754 - 但是没有办法解决这个问题。

编辑:

s_ceil.c中的一些复杂性源于它处理的特殊情况(NaN,无穷大)以及它需要在不假设存在64位整数类型的情况下完成其工作的事实。 / p>

所有bit-twiddling的基本思想是屏蔽尾数的小数位,如果数字大于零则加1 ...但是还要考虑一些额外的逻辑以确保你在所有情况下都做对了。

这是我拼凑的花车的s_ceil.c的说明性版本。注意:这正确处理特殊情况并且未经过广泛测试 - 所以不要实际使用它。然而,它确实有助于说明比特缠绕的原则。我试图广泛地评论这个例程,但评论确实假设你理解浮点数是如何用IEEE 754格式表示的。

ceil()

答案 1 :(得分:2)

您将要编写的内容比使用标准库实现更优雅。没有任何代码总是比优雅的代码更优雅。

除此之外,这种方法有两个主要缺陷:

  • 如果X大于INT_MAX + 1或小于INT_MIN - 1,则宏的行为未定义。这意味着您的实现可能会为近一半的浮点数提供不正确的结果。您还将提出无效标志,与IEEE-754相反。
  • 它得到-0,+ / - 无穷大和南错的边缘情况。事实上,唯一正确的边缘情况是+0。

可以以类似于您尝试的方式实现ceil,就像这样(此实现假定IEEE-754双精度):

#include <math.h>

double ceil(double x) {
    // All floating-point numbers larger than 2^52 are exact integers, so we
    // simply return x for those inputs.  We also handle ceil(nan) = nan here.
    if (isnan(x) || fabs(x) >= 0x1.0p52) return x;
    // Now we know that |x| < 2^52, and therefore we can use conversion to
    // long long to force truncation of x without risking undefined behavior.
    const double truncation = (long long)x;
    // If the truncation of x is smaller than x, then it is one less than the
    // desired result.  If it is greater than or equal to x, it is the result.
    // Adding one cannot produce a rounding error because `truncation` is an
    // integer smaller than 2^52.
    const double ceiling = truncation + (truncation < x);
    // Finally, we need to patch up one more thing; the standard specifies that
    // ceil(-small) be -0.0, whereas we will have 0.0 right now.  To handle this
    // correctly, we apply the sign of x to the result.
    return copysign(ceiling, x);
}

这样的事情就像你能得到的一样优雅而且仍然是正确的。


我对马丁在他的回答中提出的(通常很好的!)实施方案表示了一些担忧。以下是我将如何实施他的方法:

#include <stdint.h>
#include <string.h>

static inline uint64_t toRep(double x) {
    uint64_t r;
    memcpy(&r, &x, sizeof x);
    return r;
}

static inline double fromRep(uint64_t r) {
    double x;
    memcpy(&x, &r, sizeof x);
    return x;
}

double ceil(double x) {

    const uint64_t signbitMask  = UINT64_C(0x8000000000000000);
    const uint64_t significandMask = UINT64_C(0x000fffffffffffff);

    const uint64_t xrep = toRep(x);
    const uint64_t xabs = xrep & signbitMask;

    // If |x| is larger than 2^52 or x is NaN, the result is just x.
    if (xabs >= toRep(0x1.0p52)) return x;

    if (xabs < toRep(1.0)) {
        // If x is in (1.0, 0.0], the result is copysign(0.0, x).
        // We can generate this value by clearing everything except the signbit.
        if (x <= 0.0) return fromRep(xrep & signbitMask);
        // Otherwise x is in (0.0, 1.0), and the result is 1.0.
        else return 1.0;
    }

    // Now we know that the exponent of x is strictly in the range [0, 51],
    // which means that x contains both integral and fractional bits.  We
    // generate a mask covering the fractional bits.
    const int exponent = xabs >> 52;
    const uint64_t fractionalBits = significandMask >> exponent;

    // If x is negative, we want to truncate, so we simply mask off the
    // fractional bits.
    if (xrep & signbitMask) return fromRep(xrep & ~fractionalBits);

    // x is positive; to force rounding to go away from zero, we first *add*
    // the fractionalBits to x, then truncate the result.  The add may
    // overflow the significand into the exponent, but this produces the
    // desired result (zero significand, incremented exponent), so we just
    // let it happen.
    return fromRep(xrep + fractionalBits & ~fractionalBits);
}

关于这种方法需要注意的一点是它为非整数输入引发不精确的浮点标志。这可能与您的使用有关,也可能不是。我列出的第一个实现确实提升了标志。

答案 2 :(得分:0)

我认为宏功能不是一个好的解决方案:它不是类型安全的,并且存在对参数的多重评估(副作用)。你应该写一个干净而优雅的功能

答案 3 :(得分:0)

正如我原本期望在答案中有更多的笑话,我会尝试一些

#define CEILING(X) ceil(X)

奖金:没有太多副作用的宏 如果你不关心过多的负零

#define CEILING(X) (-floor(-(X)))

如果你关心负零,那么

#define CEILING(X) (NEGATIVE_ZERO - floor(-(X)))

NEGATIVE_ZERO的便携式定义留作锻炼.... 奖金,它还将设置FP标志(OVERFLOW INVALID INEXACT)