5

I´m looking for an alternative for the ceil() and floor() functions in C, due to I am not allowed to use these in a project.

What I have build so far is a tricky back and forth way by the use of the cast operator and with that the conversion from a floating-point value (in my case a double) into an int and later as I need the closest integers, above and below the given floating-point value, to be also double values, back to double:

#include <stdio.h>

int main(void) {
   double original = 124.576;
   double floorint;
   double ceilint;
   int f;
   int c;

   f = (int)original;            //Truncation to closest floor integer value
   c = f + 1;
   floorint = (double)f;
   ceilint = (double)c;

   printf("Original Value: %lf, Floor Int: %lf , Ceil Int: %lf", original, floorint, ceilint);
}

Output:

Original Value: 124.576000, Floor Int: 124.000000 , Ceil Int: 125.000000 

For this example normally I would not need the ceil and floor integer values of c and f to be converted back to double but I need them in double in my real program. Consider that as a requirement for the task.


Although the output is giving the desired values and seems right so far, I´m still in concern if this method is really that right and appropriate or, to say it more clearly, if this method does bring any bad behavior or issue into the program or gives me a performance-loss in comparison to other alternatives, if there are any other possible alternatives.


Do you know a better alternative? And if so, why this one should be better?

Thank you very much.

3 Answers3

3

Do you know a better alternative? And if so, why this one should be better?

OP'code fails:

  • original is already a whole number.

  • original is a negative like -1.5. Truncation is not floor there.

  • original is just outside int range.

  • original is not-a-number.


Alternative construction

double my_ceil(double x)

Using the cast to some integer type trick is a problem when x is outsize the integer range. So check first if x is inside range of a wide enough integer (one whose precision exceeds double). x values outside that are already whole numbers. Recommend to go for the widest integer (u)intmax_t.

Remember that a cast to an integer is a round toward 0 and not a floor. Different handling needed if x is negative/positive when code is ceil() or floor(). OP's code missed this.

I'd avoid if (x >= INTMAX_MAX) { as that involves (double) INTMAX_MAX whose rounding and then precise value is "chosen in an implementation-defined manner". Instead, I'd compare against INTMAX_MAX_P1. some_integer_MAX is a Mersenne Number and with 2's complement, ...MIN is a negated "power of 2".

#include <inttypes.h>

#define INTMAX_MAX_P1 ((INTMAX_MAX/2 + 1)*2.0)

double my_ceil(double x) {
  if (x >= INTMAX_MAX_P1) {
    return x;
  }
  if (x < INTMAX_MIN) {
    return x;
  }

  intmax_t i = (intmax_t) x;      // this rounds towards 0
  if (i < 0 || x == i) return i;  // negative x is already rounded up.
  return i + 1.0;
}

As x may be a not-a-number, it is more useful to reverse the compare as relational compare of a NaN is false.

double my_ceil(double x) {
  if (x >= INTMAX_MIN && x < INTMAX_MAX_P1) {
    intmax_t i = (intmax_t) x;      // this rounds towards 0
    if (i < 0 || x == i) return i;  // negative x is already rounded up.
    return i + 1.0;
  }
  return x;
}

double my_floor(double x) {
  if (x >= INTMAX_MIN && x < INTMAX_MAX_P1) {
    intmax_t i = (intmax_t) x;      // this rounds towards 0
    if (i > 0 || x == i) return i;  // positive x is already rounded down.
    return i - 1.0;
  }
  return x;
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
2

You're missing an important step: you need to check if the number is already integral, so for ceil assuming non-negative numbers (generalisation is trivial), use something like

double ceil(double f){
    if (f >= LLONG_MAX){
        // f will be integral unless you have a really funky platform
        return f;
    } else {
        long long i = f;
        return 0.0 + i + (f != i); // to obviate potential long long overflow
    }
}

Another missing piece in the puzzle, which is covered off by my enclosing if, is to check if f is within the bounds of a long long. On common platforms if f was outside the bounds of a long long then it would be integral anyway.

Note that floor is trivial due to the fact that truncation to long long is always towards zero.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • First thank you very much for your answer. Why are these two things are so important to the case? Why is it bad if i omit them? This isn´t so clear for me. – RobertS supports Monica Cellio Dec 13 '19 at 11:48
  • ~@RobertS-ReinstateMonica: The version I present is good to go assuming 64 bit IEEE754 floating point double. – Bathsheba Dec 13 '19 at 11:52
  • 1
    There is also the conundrum of how to handle NaNs and infinities. – th33lf Dec 13 '19 at 11:59
  • @th33lf: Indeed. Writing a portable `ceil` is a pain. A C standard library has it easy in that they can ignore portability. – Bathsheba Dec 13 '19 at 12:02
  • @Bathsheba What is the benefit of using a `long long int` instead of a normal `int` here in that case? – RobertS supports Monica Cellio Dec 13 '19 at 12:08
  • @Bathsheba Also: Your answer is seemingly only about the "ceiling". Is there anything to improve for the "flooring" technique as in my example? I´m not really trust in mine or are these wrong worries? – RobertS supports Monica Cellio Dec 13 '19 at 12:11
  • @RobertS-ReinstateMonica: Flooring is more trivial as truncation to `long long` is always towards zero. – Bathsheba Dec 13 '19 at 12:13
  • 1
    But you should still change the test above to if (f<=-LLONG_MIN || f>=LLONG_MAX) {.... – Hans Olsson Dec 13 '19 at 12:37
  • @HansOlsson: handling negative numbers is not so simple for `ceil` because the formula `0.0 + i + (f != i)` only works for `f >= 0`. For `f < 0` one should just use `(double)i` – chqrlie Dec 14 '19 at 15:09
  • @chqrlie - To handle that also update the return statement to e.g. return 0.0+i+(i<0?0.0:(f!=i)); - or some other variant. Currently the code just says that it is trivial to handle negative numbers, but doesn't say how. – Hans Olsson Dec 16 '19 at 11:10
1

If we need to find the ceil value of a/b, then we can use (a+b-1)/b instead of using ceil function.

Enjoy. No need to use double or float.

  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Aug 31 '23 at 12:10
  • Interesting approach, but it only works for integers and has some potential problems with `a + b` giving an overflow. – giusti Sep 01 '23 at 18:50