Функции floor() и ceil() на C++ занимаются не только округлением чисел. Изучим их эффективное использование в реальных сценариях — от финансовых расчетов до разработки игр.
Базовое применение floor() и ceil()
Сначала посмотрим на эти функции в деле:
#include <cmath>
#include <iostream>
void basic_examples() {
double numbers[] = {3.7, -3.7, 0.0, -0.0, 3.0, -3.0};
for (double num : numbers) {
std::cout << "Number: " << num << '\n'
<< "Floor: " << std::floor(num) << '\n'
<< "Ceil: " << std::ceil(num) << '\n'
<< "-------------------\n";
}
}
// Вывод:
// Число: 3.7
// Пол: 3
// Потолок: 4
// -------------------
// Число: -3.7
// Пол: -4
// Потолок: -3
// -------------------
// ... и так далее
Реальный пример: финансовые расчеты
Вот как используются floor() и ceil() для финансового округления:
class MoneyCalculator {
public:
// Округляется до цента
static double round_currency(double amount) {
return std::floor(amount * 100 + 0.5) / 100;
}
// Округляются вверх до цента, например комиссии
static double ceil_currency(double amount) {
return std::ceil(amount * 100) / 100;
}
// Округляются вниз до цента, например дисконт
static double floor_currency(double amount) {
return std::floor(amount * 100) / 100;
}
};
void financial_example() {
double price = 19.999;
double tax_rate = 0.08; // 8%-ный налог
double tax = MoneyCalculator::ceil_currency(price * tax_rate);
double final_price = MoneyCalculator::round_currency(price + tax);
std::cout << "Original price: $" << price << '\n'
<< "Tax: $" << tax << '\n'
<< "Final price: $" << final_price << '\n';
}
Разработка игр: сеточное позиционирование
Вот как floor() и ceil() используются в сеточной игровой механике:
struct Vector2D {
double x, y;
Vector2D(double x_, double y_) : x(x_), y(y_) {}
};
class GridSystem {
public:
static constexpr double CELL_SIZE = 64.0; // пиксели
static Vector2D world_to_grid(const Vector2D& world_pos) {
return Vector2D(
std::floor(world_pos.x / CELL_SIZE),
std::floor(world_pos.y / CELL_SIZE)
);
}
static Vector2D grid_to_world(const Vector2D& grid_pos) {
return Vector2D(
grid_pos.x * CELL_SIZE,
grid_pos.y * CELL_SIZE
);
}
static Vector2D snap_to_grid(const Vector2D& world_pos) {
Vector2D grid_pos = world_to_grid(world_pos);
return grid_to_world(grid_pos);
}
};
void game_example() {
Vector2D player_pos(123.45, 67.89);
Vector2D grid_pos = GridSystem::world_to_grid(player_pos);
Vector2D snapped_pos = GridSystem::snap_to_grid(player_pos);
std::cout << "Player position: (" << player_pos.x << ", " << player_pos.y << ")\n"
<< "Grid position: (" << grid_pos.x << ", " << grid_pos.y << ")\n"
<< "Snapped position: (" << snapped_pos.x << ", " << snapped_pos.y << ")\n";
}
Пользовательские функции округления
Вот набор полезных утилит округления:
class RoundingUtils {
public:
// Округляются до ближайшего кратного
static double round_to_multiple(double value, double multiple) {
return std::floor(value / multiple + 0.5) * multiple;
}
// Округляются вверх до ближайшего кратного
static double ceil_to_multiple(double value, double multiple) {
return std::ceil(value / multiple) * multiple;
}
// Округляются вниз до ближайшего кратного
static double floor_to_multiple(double value, double multiple) {
return std::floor(value / multiple) * multiple;
}
// Округляются до указанных знаков после запятой
static double round_to_decimal(double value, int decimal_places) {
double multiplier = std::pow(10.0, decimal_places);
return std::floor(value * multiplier + 0.5) / multiplier;
}
};
void demonstrate_rounding() {
double value = 47.123;
std::cout << "Original value: " << value << '\n'
<< "Rounded to nearest 5: " << RoundingUtils::round_to_multiple(value, 5.0) << '\n'
<< "Ceiling to nearest 5: " << RoundingUtils::ceil_to_multiple(value, 5.0) << '\n'
<< "Floor to nearest 5: " << RoundingUtils::floor_to_multiple(value, 5.0) << '\n'
<< "Rounded to 2 decimals: " << RoundingUtils::round_to_decimal(value, 2) << '\n';
}
Пример распределения ресурсов
Вот как при помощи ceil() рассчитывается выделение памяти:
class ResourceAllocator {
public:
static constexpr size_t BLOCK_SIZE = 4096; // Блоки 4 Кб
static size_t calculate_blocks_needed(size_t bytes) {
return static_cast<size_t>(std::ceil(static_cast<double>(bytes) / BLOCK_SIZE));
}
static size_t calculate_allocation_size(size_t bytes) {
return calculate_blocks_needed(bytes) * BLOCK_SIZE;
}
static void print_allocation_info(size_t requested_bytes) {
size_t blocks = calculate_blocks_needed(requested_bytes);
size_t actual_bytes = calculate_allocation_size(requested_bytes);
std::cout << "Requested: " << requested_bytes << " bytes\n"
<< "Blocks needed: " << blocks << '\n'
<< "Actual allocation: " << actual_bytes << " bytes\n"
<< "Wasted space: " << actual_bytes - requested_bytes << " bytes\n";
}
};
void allocation_example() {
ResourceAllocator::print_allocation_info(6000); // Больше, чем один блок
ResourceAllocator::print_allocation_info(2048); // Меньше, чем один блок
}
Работа с временными интервалами
А так floor() и ceil() справляются с вычислениями времени:
class TimeCalculator {
public:
static constexpr int SECONDS_PER_MINUTE = 60;
static constexpr int SECONDS_PER_HOUR = 3600;
static int ceil_minutes(double seconds) {
return static_cast<int>(std::ceil(seconds / SECONDS_PER_MINUTE));
}
static int floor_minutes(double seconds) {
return static_cast<int>(std::floor(seconds / SECONDS_PER_MINUTE));
}
static std::string format_time_range(double seconds) {
int min_minutes = floor_minutes(seconds);
int max_minutes = ceil_minutes(seconds);
if (min_minutes == max_minutes) {
return std::to_string(min_minutes) + " minutes";
}
return std::to_string(min_minutes) + "-" +
std::to_string(max_minutes) + " minutes";
}
static int calculate_billing_units(double hours, double unit_size = 0.25) {
return static_cast<int>(std::ceil(hours / unit_size));
}
};
void time_example() {
double task_duration = 127.5; // секунды
std::cout << "Task will take: " << TimeCalculator::format_time_range(task_duration) << '\n';
double meeting_hours = 1.6; // один час 36 минут
int billing_units = TimeCalculator::calculate_billing_units(meeting_hours);
std::cout << "Billable units (15-minute intervals): " << billing_units << '\n';
}
Типичные проблемы и их решения
- Проблемы точности вычислений с плавающей точкой:
double value = 2.0;
// Не делайте этого:
if (std::floor(value) == value) { // Не выполнится из-за точности
std::cout << "Integer value\n";
}
// Делайте так:
const double epsilon = 1e-10;
if (std::abs(std::floor(value) - value) < epsilon) {
std::cout << "Integer value\n";
}
2. Целочисленное переполнение при приведении типов:
class SafeRounding {
public:
static int safe_floor(double value) {
if (value > std::numeric_limits<int>::max() ||
value < std::numeric_limits<int>::min()) {
throw std::overflow_error("Value out of integer range");
}
return static_cast<int>(std::floor(value));
}
};
Не забывайте, что floor() и ceil() предназначены для типов с плавающей точкой float, double, long double. Для целочисленных типов эти операции излишни, поскольку целые числа уже «округлены» по определению.
Читайте также:
- C++: полное руководство по вставке в векторах
- C++: полное руководство по std::stoi
- C++: полное руководство по динамическим массивам
Читайте нас в Telegram, VK и Дзен
Перевод статьи ryan: Floor and Ceil Functions in C++: Complete Guide





