Введение
Приложения, работающие с физическими величинами или объёмами данных, регулярно нуждаются в конвертации единиц измерения — расстояния, объёмов памяти, температуры. Разрозненные magic-числа конвертации, разбросанные по коду (value * 3.28084 без пояснений), быстро становятся источником ошибок и путаницы. В статье построим компактный, расширяемый helper-класс для конвертации единиц измерения.
Концепция
Удобный подход — выделить отдельный класс-утилиту с явно именованными статическими методами конвертации (либо набор constexpr-функций), группированными по категории величин (длина, объём данных, температура). Использование строго типизированных enum-значений для единиц измерения вместо «магических» множителей напрямую в коде делает конвертацию явной и самодокументируемой на местах вызова.
Пример кода
#pragma once
#include <array>
namespace UnitConverter {
// --- Конвертация длины ---
enum class LengthUnit { Meters, Feet, Inches, Kilometers, Miles };
constexpr double toMeters(double value, LengthUnit unit)
{
switch (unit) {
case LengthUnit::Meters: return value;
case LengthUnit::Feet: return value * 0.3048;
case LengthUnit::Inches: return value * 0.0254;
case LengthUnit::Kilometers: return value * 1000.0;
case LengthUnit::Miles: return value * 1609.344;
}
return value;
}
constexpr double convertLength(double value, LengthUnit from, LengthUnit to)
{
const double meters = toMeters(value, from);
// Конвертация из метров в целевую единицу — обратное преобразование
switch (to) {
case LengthUnit::Meters: return meters;
case LengthUnit::Feet: return meters / 0.3048;
case LengthUnit::Inches: return meters / 0.0254;
case LengthUnit::Kilometers: return meters / 1000.0;
case LengthUnit::Miles: return meters / 1609.344;
}
return meters;
}
// --- Конвертация объёма данных ---
enum class DataSizeUnit { Bytes, Kilobytes, Megabytes, Gigabytes };
constexpr double toBytes(double value, DataSizeUnit unit, bool useBinaryBase = true)
{
const double base = useBinaryBase ? 1024.0 : 1000.0;
switch (unit) {
case DataSizeUnit::Bytes: return value;
case DataSizeUnit::Kilobytes: return value * base;
case DataSizeUnit::Megabytes: return value * base * base;
case DataSizeUnit::Gigabytes: return value * base * base * base;
}
return value;
}
} // namespace UnitConverter
// Использование
#include "unitconverter.h"
#include <QDebug>
void demonstrateUsage()
{
using namespace UnitConverter;
double distanceInMiles = convertLength(10.0, LengthUnit::Kilometers, LengthUnit::Miles);
qDebug() << "10 км =" << distanceInMiles << "миль";
double fileSizeInBytes = toBytes(150.0, DataSizeUnit::Megabytes);
qDebug() << "150 МБ =" << fileSizeInBytes << "байт";
}
Пояснения к коду
LengthUnit и DataSizeUnit — строго типизированные перечисления, исключающие путаницу между, например, метрами и футами на местах вызова — компилятор не позволит передать число там, где ожидается значение enum, что устраняет целый класс ошибок невнимательности. toMeters()/convertLength() помечены constexpr, что позволяет компилятору вычислять конвертацию во время компиляции для констант, известных на этапе сборки, без накладных расходов на выполнение в рантайме.
Параметр useBinaryBase в toBytes() явно разделяет два конкурирующих определения «килобайта» — 1024 байта (двоичная база, исторически распространённая в ОС) и 1000 байт (десятичная база, формально соответствующая префиксам СИ и использующаяся, например, в маркетинге объёма накопителей) — без этого разделения конвертация объёма данных часто становится источником путаницы.
Подводные камни
- Путаница между двоичными (1024) и десятичными (1000) приставками для объёма данных — несоответствие ожиданий пользователя (часто привыкшего к 1024) и фактически использованной базы конвертации в коде — частый источник отчётов «неправильный размер файла» от пользователей, хотя расчёт формально корректен для выбранной базы.
- Накопление погрешности округления при цепочке конвертаций (метры → футы → метры через промежуточные округления) — для критичных вычислений стоит проводить полный путь конвертации через единую базовую единицу за один проход, как сделано в
convertLengthчерез промежуточное приведение к метрам, а не последовательно применять несколько отдельных множителей. - Использование
doubleдля очень больших объёмов данных (петабайты и больше) может приводить к потере точности из-за ограниченной мантиссы — для финансовых или точных научных расчётов стоит рассмотретьlong doubleили специализированные типы с фиксированной точностью. - Жёстко зашитые константы конвертации без указания источника/стандарта. Некоторые величины имеют незначительные различия в зависимости от используемого стандарта (например, «морская миля» против обычной «мили») — стоит явно документировать, какой именно стандарт используется, особенно для специализированных доменов (навигация, авиация).