Функция Ceil: как мы можем реализовать это сами?

Я знаю, что C++ предоставляет нам функцию ceil. Для практики мне было интересно, как мы можем реализовать функцию ceil в C++. Сигнатура метода - public static int ceil (float num)

Пожалуйста, предоставьте некоторое понимание.

Я подумал о простом способе: преобразовать num в строку, найти индекс десятичной точки, проверить, больше ли десятичная часть, чем 0. Если да, вернуть num+1, иначе вернуть num. Но я хочу избежать использования преобразования строк

10 ответов

Решение

Вот наивная реализация для положительных чисел (используется тот факт, что приведение к (int) усекает до нуля):

int ceil(float num) {
    int inum = (int)num;
    if (num == (float)inum) {
        return inum;
    }
    return inum + 1;
}

Это легко распространить и на отрицательные числа.

Ваш вопрос задан для возврата функции int, но обычно ceil() Функция возвращает тот же тип, что и ее аргумент, поэтому проблем с диапазоном нет (то есть float ceil(float num)). Например, приведенная выше функция завершится ошибкой, если num 1е20.

Вы можете разобрать компоненты числа с плавающей запятой IEEE754 и реализовать логику самостоятельно:

#include <cstring>

float my_ceil(float f)
{
    unsigned input;
    memcpy(&input, &f, 4);
    int exponent = ((input >> 23) & 255) - 127;
    if (exponent < 0) return (f > 0);
    // small numbers get rounded to 0 or 1, depending on their sign

    int fractional_bits = 23 - exponent;
    if (fractional_bits <= 0) return f;
    // numbers without fractional bits are mapped to themselves

    unsigned integral_mask = 0xffffffff << fractional_bits;
    unsigned output = input & integral_mask;
    // round the number down by masking out the fractional bits

    memcpy(&f, &output, 4);
    if (f > 0 && output != input) ++f;
    // positive numbers need to be rounded up, not down

    return f;
}

(Вставьте обычный отказ от ответственности здесь.)

По сути, это то, что вы должны сделать, но без преобразования в string,

Число с плавающей точкой представляется как (+/-) M * 2^E, Экспонента, E, говорит вам, как далеко вы находитесь от двоичной точки *. Если E достаточно большой, дробной части нет, так что делать нечего. Если E достаточно мала, целочисленная часть отсутствует, поэтому ответ равен 1 (при условии M ненулевое, а число положительное). Иначе, E сообщает вам, где в вашей мантиссе появляется двоичная точка, которую вы можете использовать для проверки, а затем выполнить округление.


* Не десятичная точка, потому что мы в базе-2, а не в базе-10.

Мои 5 центов:

template <typename F>
constexpr inline auto ceil(F const f) noexcept
{
  auto const t(std::trunc(f));

  return t + (t < f);
}

Предыдущая рекомендация кода:

int ceil(float val) 
{
    int temp  = val * 10;
    if(val%10)
    return (temp+1);
    else
    return temp;
}

не компилируется: получает сообщение об ошибке "C2296:"% ": недопустимо, левый операнд имеет тип" float "" в строке 4 if(val%10)", потому что вы не можете использовать оператор mod (%) для float или двойной. См.: Почему мы не можем использовать оператор% для операндов с плавающей точкой и двойного типа? Он также не работает для десятичных значений, точность которых не превышает 1/10.

Принимая во внимание, что предыдущая рекомендация кода:

int ma_ceil(float num)
{   int a = num;
    if ((float)a != num)
        return num+1;
    return num;
}

работает хорошо, пока вы не выходите за пределы значения с плавающей запятой. число = 555555555; или num = -5.000000001 не будут работать, если вы не используете двойной.

Кроме того, поскольку числа с плавающей запятой и числа с двойными числами хранятся в формате IEEE, хранимые двоичные представления могут быть неточными. Например:

число с плавающей запятой = 5; в некоторых случаях может быть не присвоено значение 5.0000000, а 5.9999998 или 5.00000001. Чтобы исправить предыдущую версию кода, я бы рекомендовал изменить возвращаемое значение, чтобы использовать целочисленную математику, а не полагаться на точность значения с плавающей запятой, следующим образом:

int ma_ceil(float num)
{   int a = num;
    if ((float)a != num)
        return a+1;
    return a;
}

Вот так (чисто из первых принципов):

      #include <iostream>
#include <type_traits>


template<typename T>
constexpr int truncate( const T val )
{
    return static_cast<int>( val );
}

template<typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
constexpr T modulusFloat( const T divident,
    const T divisor )
{
    return divident - truncate( divident / divisor ) * divisor;
}

template<typename T>
constexpr T modulus( const T divident,
    const T divisor ) noexcept
{
    if constexpr ( std::is_floating_point_v<T> )
    {
        return modulusFloat( divident,
            divisor );
    }
    else
    {
        return divident % divisor;
    }
}

template<typename T>
constexpr T ceil( const T val ) noexcept
{
    return val + (T)1 - modulus( val, (T)1 );
}

int main()
{
    std::cout << ceil( 5.4 ) << '\n';
    std::cout << ceil( -5.4 ) << '\n';

    return 0;
}

Вот тот, который работает с отрицательными числами:

int ceil(float num) {
    int inum = (int)num;
    if (num < 0 || num == (float)inum) {
        return inum;
    }
    return inum + 1;
}

Что-то вроде этого:

  double param, fractpart, intpart;

  param = 3.14159265;
  fractpart = modf (param , &intpart);

  int intv = static_cast<int>(intpart); // can overflow - so handle that.

  if (fractpart > some_epsilon)
    ++intv;

Вам просто нужно определить some_epsilon Значение того, что вы хотите, чтобы дробная часть была больше, чем до увеличения целочисленной части. Другие вещи, которые следует учитывать, это знак (т.е., если значение отрицательное и т. Д.)

Тоже работает с отрицательным значением

int ma_ceil(float num)
{   int a = num;
    if ((float)a != num)
        return num+1;

    return num;
}

Попробуй это...

int ceil(float val)
{
    int temp  = val * 10;
    if(val%10)
    return (temp+1);
    else
    return temp;
}
Другие вопросы по тегам