首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用C语言实现ceil()

问用C语言实现ceil()
EN

Stack Overflow用户
提问于 2012-09-05 18:57:12
回答 4查看 6K关注 0票数 4

我想用C实现我自己的ceil()。在库中搜索源代码并找到了here,但它似乎很难理解。我想要干净和优雅的代码。

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

代码语言:javascript
复制
#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。宏在这里是类型安全的吗?此外,上面的实现对负数有效吗?

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

你能提供干净的代码吗?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2012-09-05 19:04:04

对于大于INT_MAX的数字,您引用的宏肯定不能正确工作,但仍然可以精确地表示为双精度数。

正确实现ceil()的唯一方法(假设您不能使用等效的汇编指令来实现它)是对浮点数的二进制表示进行位旋转,就像在第一个链接后面的s_ceil.c源文件中所做的那样。要理解代码的工作原理,需要理解底层平台的浮点表示法--表示法很可能是IEEE 754 --但这是没有办法的。

编辑:

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

所有位旋转的基本思想是屏蔽尾数的小数位,如果尾数大于零,则将其加1...但是,为了确保您在所有情况下都做正确的事情,还涉及到一些额外的逻辑。

这是我拼凑而成的浮点数的ceil()的一个说明性版本。注意:这不能正确处理特殊情况,也没有经过广泛的测试--所以不要实际使用它。然而,它确实有助于说明在比特摆弄中涉及的原则。我已经尝试对该例程进行了广泛的注释,但注释确实假设您了解如何在IEEE754格式中表示浮点数。

代码语言:javascript
复制
union float_int
{
    float f;
    int i;
};

float myceil(float x)
{
    float_int val;
    val.f=x;

    // Extract sign, exponent and mantissa
    // Bias is removed from exponent
    int sign=val.i >> 31;
    int exponent=((val.i & 0x7fffffff) >> 23) - 127;
    int mantissa=val.i & 0x7fffff;

    // Is the exponent less than zero?
    if(exponent<0) 
    {   
        // In this case, x is in the open interval (-1, 1)
        if(x<=0.0f)
            return 0.0f;
        else
            return 1.0f;
    }
    else
    {
        // Construct a bit mask that will mask off the
        // fractional part of the mantissa
        int mask=0x7fffff >> exponent;

        // Is x already an integer (i.e. are all the
        // fractional bits zero?)
        if((mantissa & mask) == 0)
            return x;
        else
        {
            // If x is positive, we need to add 1 to it
            // before clearing the fractional bits
            if(!sign)
            {
                mantissa+=1 << (23-exponent);

                // Did the mantissa overflow?
                if(mantissa & 0x800000)
                {
                    // The mantissa can only overflow if all the
                    // integer bits were previously 1 -- so we can
                    // just clear out the mantissa and increment
                    // the exponent
                    mantissa=0;
                    exponent++;
                }
            }

            // Clear the fractional bits
            mantissa&=~mask;
        }
    }

    // Put sign, exponent and mantissa together again
    val.i=(sign << 31) | ((exponent+127) << 23) | mantissa;

    return val.f;
}
票数 2
EN

Stack Overflow用户

发布于 2012-09-05 19:06:22

没有什么比使用标准库实现更优雅的了。没有代码总是比优雅的代码更优雅。

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

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

您可以使用类似于您尝试的方式来实现ceil,如下所示(此实现假设IEEE-754双精度):

代码语言:javascript
复制
#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);
}

像这样的东西是你能得到的最优雅的东西,而且仍然是正确的。

我指出了一些问题(总体上是好的!)马丁在他的答案中提出的实现。下面是我将如何实现他的方法:

代码语言:javascript
复制
#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
EN

Stack Overflow用户

发布于 2012-09-05 19:01:28

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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/12279914

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档