Какие существуют типы вещественных данных и в чем их особенности?
Вещественные данные нужны для величин, которые могут иметь дробную часть: координат, времени, температуры, вероятности, результатов измерений и научных расчетов. В математике вещественных чисел бесконечно много, а память компьютера конечна. Поэтому программа хранит не произвольное вещественное число, а одно из конечного набора его машинных приближений.
Чаще всего такое приближение представлено числом с плавающей точкой. Для задач, где важны именно десятичные разряды, применяют десятичные и многоточностные типы.
Как число с плавающей точкой хранится в памяти
Стандарт IEEE 754 делит двоичное число на три поля:
- знак определяет положительное или отрицательное значение;
- порядок задает масштаб числа;
- значащая часть хранит его значимые двоичные цифры.
Упрощенная формула имеет вид:
значение = знак × значащая часть × 2^порядок
Положение двоичной точки меняется вместе с порядком — отсюда название «плавающая точка». Больший порядок расширяет диапазон, а большее число битов значащей части повышает точность.
Основные вещественные представления
| Представление | Типичный размер | Примерная десятичная точность | Когда используют |
|---|---|---|---|
binary32, часто float | 32 бита | около 7 значащих цифр | графика, нейросети, большие массивы приблизительных данных |
binary64, часто double | 64 бита | около 15–16 значащих цифр | расчеты общего назначения, статистика, инженерные задачи |
расширенная точность, например long double | зависит от платформы | не меньше точности double, но формат зависит от реализации | особо чувствительные промежуточные расчеты |
десятичное представление, например decimal | зависит от языка | задается форматом или контекстом вычислений | деньги, налоги, учет с установленными правилами округления |
| произвольная точность | растет вместе с числом | задается программой и доступной памятью | криптография, исследовательские и символьные вычисления |
Названия не полностью совпадают между языками:
- C, C++ и Java предоставляют
floatиdouble; - Go —
float32иfloat64; - JavaScript и TypeScript хранят обычный
numberв формате binary64; - Python обычно реализует
floatкак binary64; - C# предоставляет
decimal, а Java — классBigDecimalдля десятичных вычислений.
Размер и свойства long double нужно проверять для конкретного компилятора и платформы: название само по себе не гарантирует 80- или 128-битный формат.
Почему возникает погрешность
Дробь 0,1 конечна в десятичной записи, но в двоичной системе она периодическая. В отведенное число битов помещается только округленное значение. Поэтому результат нескольких операций может немного отличаться от математически точного:
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // falsepackage main
import "fmt"
func main() {
left := 0.1
right := 0.2
fmt.Printf("%.17f
", left+right) // 0.30000000000000004
}public class Example {
public static void main(String[] args) {
double sum = 0.1 + 0.2;
System.out.println(sum); // 0.30000000000000004
}
}print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # FalseЭто не случайная ошибка программы, а следствие выбранного формата. Погрешность может накапливаться, а порядок операций — влиять на последний разряд результата.
Точность и диапазон — разные свойства. Число с большим порядком может иметь огромный модуль, но расстояние между соседними представимыми значениями тоже станет больше. Очень маленькие значения могут перейти в область субнормальных чисел, потерять часть точности, а затем округлиться до нуля. Слишком большое по модулю значение приводит к переполнению.
Специальные значения IEEE 754
Двоичные форматы с плавающей точкой предусматривают:
- положительный и отрицательный ноль;
- положительную и отрицательную бесконечность;
NaN— «не число» для неопределенного результата;- субнормальные числа около нуля.
Поведение конкретной операции определяет язык. Например, JavaScript возвращает Infinity при 1 / 0, а Python для 1.0 / 0.0 выбрасывает ZeroDivisionError. Поэтому нельзя переносить правила одного языка в другой без проверки.
NaN не равен даже самому себе. Для проверки в JavaScript используют Number.isNaN(value), в Python — math.isnan(value).
Как правильно сравнивать результаты
Одна постоянная абсолютная погрешность плохо работает одновременно для чисел около нуля и очень больших чисел. Надежнее сочетать абсолютный и относительный допуск:
function nearlyEqual(
left: number,
right: number,
absoluteTolerance = 1e-12,
relativeTolerance = 1e-9,
): boolean {
const difference = Math.abs(left - right);
const scale = Math.max(Math.abs(left), Math.abs(right));
const tolerance = Math.max(absoluteTolerance, relativeTolerance * scale);
return difference <= tolerance;
}
console.log(nearlyEqual(0.1 + 0.2, 0.3)); // trueДопуск выбирают из смысла задачи: для температуры, координат и финансов он будет разным. Точное равенство допустимо, когда сравниваются заведомо одинаковые сохраненные значения, целые числа в безопасном диапазоне или специальные маркеры состояния.
Что использовать для денежных расчетов
Двоичный float или double неудобен для сумм, которые должны точно соответствовать десятичным копейкам. Практические варианты:
- хранить сумму целым числом минимальных денежных единиц, например 19 990 копеек;
- использовать десятичный тип с явно заданными точностью и правилом округления;
- не смешивать значения разных валют и масштабов без преобразования.
const priceKopecks = 19_990;
const quantity = 3;
const totalKopecks = priceKopecks * quantity;
console.log(totalKopecks); // 59970Десятичный тип не делает любую операцию абсолютно точной: например, результат 1 / 3 все равно потребует ограничения числа знаков и правила округления.
Как выбрать тип
- выбирайте binary64 или
doubleкак разумный вариант по умолчанию для большинства вычислений; - выбирайте binary32 или
float, когда экономия памяти и пропускная способность важнее дополнительных разрядов точности; - используйте decimal или целые минимальные единицы для регламентированных денежных расчетов;
- применяйте произвольную или расширенную точность только после оценки требуемой погрешности и стоимости вычислений;
- проверяйте переполнение,
NaN, бесконечность и округление на границах допустимого диапазона.
Вывод
Вещественный тип — это ограниченное машинное представление числа, а не само математическое множество вещественных чисел. float, double, десятичные и многоточностные типы отличаются форматом, диапазоном, точностью, расходом памяти и правилами округления. Правильный выбор зависит от допустимой ошибки и смысла данных.
Источники
- Семакин И.Г. Основы программирования и баз данных. Учебник. - М.: Академия, 2014. - 224 с.
- Симонова Е.В. Структуры данных в C#. Линейные и нелинейные динамические структуры. - Лань, 2018. - 152 с.
- Бертран Мейер. Почувствуй класс. Учимся программировать хорошо с объектами и контрактами. - М.: Национальный Открытый Университет "ИНТУИТ": БИНОМ. Лаборатория знаний, 2011. - 775 с.
- Мэтт Вайсфельд. Объектно-ориентированное мышление. - СПб.: Питер, 2014. - 304 с.
- Ривест Р., Штайн К., Лейзерсон Ч., Кормен Т. Алгоритмы: построение и анализ. - М.: Вильямс, 2007. - 1296 с.
- Стивенс Род. Алгоритмы. Теория и практическое применение. - М.: Эксмо, 2017. - 544 с.
- Рублев В.С. Основы теории алгоритмов. - 2-е издание, исправленное. - М.: Научный мир, 2008. - 128 с.
- Гольдберг Г.Л. Основы алгоритмизации и программирования. - М.: Академия, 2012. - 384 с.
- Лаврищева И.В. Технология программирования. - М.: Горячая линия - Телеком, 2011. - 400 с.
- Сергеев И.С., Сухоруков А.И., Шестаков А.А. Программирование: учебник для вузов. - М.: БИНОМ. Лаборатория знаний, 2013. - 512 с.