Implement ceil() in C

有些话、适合烂在心里 提交于 2021-01-27 15:11:23

问题


I want to implement my own ceil() in C. Searched through the libraries for source code & found here, but it seems pretty difficult to understand. I want clean & elegant code.

I also searched on SO, found some answer here. None of the answer seems to be correct. One of the answer is:

#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, the return type of the ceil() is not int. Will macro be type-safe here? Further, will the above implementation work for negative numbers?

What will be the best way to implement it?

Can you provide the clean code?


回答1:


The macro you quoted definitely won't work correctly for numbers that are greater than INT_MAX but which can still be represented exactly as a double.

The only way to implement ceil() correctly (assuming you can't implement it using an equivalent assembly instruction) is to do bit-twiddling on the binary representation of the floating point number, as is done in the s_ceil.c source file behind your first link. Understanding how the code works requires an understanding of the floating point representation of the underlying platform -- the representation is most probably going to be IEEE 754 -- but there's no way around this.

Edit:

Some of the complexities in s_ceil.c stem from the special cases it handles (NaNs, infinities) and the fact that it needs to do its work without being able to assume that a 64-bit integral type exists.

The basic idea of all the bit-twiddling is to mask off the fractional bits of the mantissa and add 1 to it if the number is greater than zero... but there's a bit of additional logic involved as well to make sure you do the right thing in all cases.

Here's a illustrative version of ceil() for floats that I cobbled together. Beware: This does not handle the special cases correctly and it is not tested extensively -- so don't actually use it. It does however serve to illustrate the principles involved in the bit-twiddling. I've tried to comment the routine extensively, but the comments do assume that you understand how floating point numbers are represented in IEEE 754 format.

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:


Nothing you will write is more elegant than using the standard library implementation. No code at all is always more elegant than elegant code.

That aside, this approach has two major flaws:

  • If X is greater than INT_MAX + 1 or less than INT_MIN - 1, the behavior of your macro is undefined. This means that your implementation may give incorrect results for nearly half of all floating-point numbers. You will also raise the invalid flag, contrary to IEEE-754.
  • It gets the edge cases for -0, +/-infinity, and nan wrong. In fact, the only edge case it gets right is +0.

You can implement ceil in manner similar to what you tried, like so (this implementation assumes IEEE-754 double precision):

#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);
}

Something like that is about as elegant as you can get and still be correct.


I flagged a number of concerns with the (generally good!) implementation that Martin put in his answer. Here's how I would implement his approach:

#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);
}

One thing to note about this approach is that it does not raise the inexact floating-point flag for non-integral inputs. That may or may not be a concern for your usage. The first implementation that I listed does raise the flag.




回答3:


As I would have expected more jokes in answers, I will try a couple

#define CEILING(X) ceil(X)

Bonus: a macro with not so many side effects
If you don't care too much of negative zeroes

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

If you care of negative zero, then

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

Portable definition of NEGATIVE_ZERO left as an exercize.... Bonus, it will also set FP flags (OVERFLOW INVALID INEXACT)




回答4:


I don't think a macrofunction is a good solution: it isn't type safe and there is a multi-evaluation of the arguments (side-effects). You should rather write a clean and elegant function.



来源:https://stackoverflow.com/questions/12279914/implement-ceil-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!