Какие существуют типы вещественных данных и в чем их особенности?

Вещественные данные нужны для величин, которые могут иметь дробную часть: координат, времени, температуры, вероятности, результатов измерений и научных расчетов. В математике вещественных чисел бесконечно много, а память компьютера конечна. Поэтому программа хранит не произвольное вещественное число, а одно из конечного набора его машинных приближений.

Чаще всего такое приближение представлено числом с плавающей точкой. Для задач, где важны именно десятичные разряды, применяют десятичные и многоточностные типы.

Как число с плавающей точкой хранится в памяти

Стандарт IEEE 754 делит двоичное число на три поля:

Упрощенная формула имеет вид:

значение = знак × значащая часть × 2^порядок

Положение двоичной точки меняется вместе с порядком — отсюда название «плавающая точка». Больший порядок расширяет диапазон, а большее число битов значащей части повышает точность.

Основные вещественные представления

ПредставлениеТипичный размерПримерная десятичная точностьКогда используют
binary32, часто float32 битаоколо 7 значащих цифрграфика, нейросети, большие массивы приблизительных данных
binary64, часто double64 битаоколо 15–16 значащих цифррасчеты общего назначения, статистика, инженерные задачи
расширенная точность, например long doubleзависит от платформыне меньше точности double, но формат зависит от реализацииособо чувствительные промежуточные расчеты
десятичное представление, например decimalзависит от языказадается форматом или контекстом вычисленийденьги, налоги, учет с установленными правилами округления
произвольная точностьрастет вместе с числомзадается программой и доступной памятьюкриптография, исследовательские и символьные вычисления

Названия не полностью совпадают между языками:

Размер и свойства long double нужно проверять для конкретного компилятора и платформы: название само по себе не гарантирует 80- или 128-битный формат.

Почему возникает погрешность

Дробь 0,1 конечна в десятичной записи, но в двоичной системе она периодическая. В отведенное число битов помещается только округленное значение. Поэтому результат нескольких операций может немного отличаться от математически точного:

console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false
package 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

Двоичные форматы с плавающей точкой предусматривают:

Поведение конкретной операции определяет язык. Например, 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 неудобен для сумм, которые должны точно соответствовать десятичным копейкам. Практические варианты:

const priceKopecks = 19_990;
const quantity = 3;
const totalKopecks = priceKopecks * quantity;

console.log(totalKopecks); // 59970

Десятичный тип не делает любую операцию абсолютно точной: например, результат 1 / 3 все равно потребует ограничения числа знаков и правила округления.

Как выбрать тип

Вывод

Вещественный тип — это ограниченное машинное представление числа, а не само математическое множество вещественных чисел. float, double, десятичные и многоточностные типы отличаются форматом, диапазоном, точностью, расходом памяти и правилами округления. Правильный выбор зависит от допустимой ошибки и смысла данных.

Источники

  • Семакин И.Г. Основы программирования и баз данных. Учебник. - М.: Академия, 2014. - 224 с.
  • Симонова Е.В. Структуры данных в C#. Линейные и нелинейные динамические структуры. - Лань, 2018. - 152 с.
  • Бертран Мейер. Почувствуй класс. Учимся программировать хорошо с объектами и контрактами. - М.: Национальный Открытый Университет "ИНТУИТ": БИНОМ. Лаборатория знаний, 2011. - 775 с.
  • Мэтт Вайсфельд. Объектно-ориентированное мышление. - СПб.: Питер, 2014. - 304 с.
  • Ривест Р., Штайн К., Лейзерсон Ч., Кормен Т. Алгоритмы: построение и анализ. - М.: Вильямс, 2007. - 1296 с.
  • Стивенс Род. Алгоритмы. Теория и практическое применение. - М.: Эксмо, 2017. - 544 с.
  • Рублев В.С. Основы теории алгоритмов. - 2-е издание, исправленное. - М.: Научный мир, 2008. - 128 с.
  • Гольдберг Г.Л. Основы алгоритмизации и программирования. - М.: Академия, 2012. - 384 с.
  • Лаврищева И.В. Технология программирования. - М.: Горячая линия - Телеком, 2011. - 400 с.
  • Сергеев И.С., Сухоруков А.И., Шестаков А.А. Программирование: учебник для вузов. - М.: БИНОМ. Лаборатория знаний, 2013. - 512 с.