Функция 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;
}