Округление чисел

При работе с формами в JavaScript значения, приходящие от пользователя, почти всегда представлены строками. Даже числовые поля HTML-форм возвращают "12.34" вместо 12.34. В контексте валидации с использованием Yup важной задачей становится не только проверка корректности числа, но и его приведение к нужному виду, включая округление.

Округление в схемах Yup не является отдельной встроенной операцией, однако оно легко реализуется через механизм преобразования (transform), который позволяет изменять входное значение до применения правил валидации.


Приведение строк к числам перед округлением

Любая логика округления должна начинаться с нормализации типа данных. Типичный сценарий — преобразование строки в число:

import * as Yup from 'yup';

const schema = Yup.object({
  price: Yup.number()
    .transform((value, originalValue) => {
      if (typeof originalValue === 'string') {
        return Number(originalValue.replace(',', '.'));
      }
      return value;
    })
});

Здесь учитывается частая проблема локализации, когда десятичный разделитель может быть запятой.


Базовое округление через Math.round

После приведения значения к числу можно применить стандартные функции JavaScript для округления. В Yup это делается внутри transform, так как сам валидатор не изменяет значение автоматически.

const schema = Yup.object({
  price: Yup.number()
    .transform((value, originalValue) => {
      const parsed = Number(originalValue);
      if (isNaN(parsed)) return value;
      return Math.round(parsed);
    })
});

В этом случае:

  • 12.713
  • 12.212

Округление до фиксированного количества знаков после запятой

Часто требуется не целое число, а округление до 2 знаков после запятой, например для денежных значений.

const schema = Yup.object({
  price: Yup.number()
    .transform((value, originalValue) => {
      const parsed = Number(originalValue);
      if (isNaN(parsed)) return value;

      return Math.round(parsed * 100) / 100;
    })
});

Механизм основан на масштабировании числа:

  • умножение на 100
  • округление
  • деление обратно

Использование toFixed внутри transform

Альтернативный способ — применение toFixed, однако важно помнить, что он возвращает строку, поэтому требуется дополнительное преобразование:

const schema = Yup.object({
  price: Yup.number()
    .transform((value, originalValue) => {
      const parsed = Number(originalValue);
      if (isNaN(parsed)) return value;

      return Number(parsed.toFixed(2));
    })
});

Этот вариант часто используется, когда важно строгое форматирование до заданной точности.


Округление вниз и вверх

Помимо стандартного математического округления, часто требуется управление направлением округления:

Округление вниз

const schema = Yup.object({
  price: Yup.number()
    .transform((value, originalValue) => {
      const parsed = Number(originalValue);
      if (isNaN(parsed)) return value;

      return Math.floor(parsed * 100) / 100;
    })
});

Округление вверх

const schema = Yup.object({
  price: Yup.number()
    .transform((value, originalValue) => {
      const parsed = Number(originalValue);
      if (isNaN(parsed)) return value;

      return Math.ceil(parsed * 100) / 100;
    })
});

Сохранение исходного значения и условное округление

Иногда округление должно применяться только при успешной валидации, чтобы избежать потери точности при ошибочных данных:

const schema = Yup.number()
  .transform((value, originalValue) => {
    const parsed = Number(originalValue);
    return isNaN(parsed) ? value : parsed;
  })
  .test('rounding', function (value) {
    if (typeof value !== 'number') return true;

    const rounded = Math.round(value * 100) / 100;

    if (value !== rounded) {
      return this.createError({
        message: 'Число должно быть округлено до 2 знаков'
      });
    }

    return true;
  });

Комбинирование округления с валидацией диапазонов

Округлённые значения часто сразу проходят дополнительные проверки:

const schema = Yup.object({
  rating: Yup.number()
    .transform((value, originalValue) => {
      const parsed = Number(originalValue);
      return isNaN(parsed) ? value : Math.round(parsed * 10) / 10;
    })
    .min(0)
    .max(5)
});

Здесь значение:

  • приводится к числу
  • округляется до 1 знака после запятой
  • проверяется на диапазон

Особенности поведения transform

Механизм transform в Yup имеет несколько важных особенностей:

  • выполняется до валидации (test, min, max)
  • может изменять тип значения
  • влияет на итоговый результат cast()
  • применяется даже при strict(false)

Это делает его основным инструментом для реализации округления.


Округление как часть бизнес-логики схемы

Использование схемы для округления позволяет централизовать правила обработки чисел:

const moneySchema = Yup.number().transform((value, originalValue) => {
  const parsed = Number(originalValue);
  if (isNaN(parsed)) return value;

  return Math.round(parsed * 100) / 100;
});

Такая схема становится переиспользуемой и обеспечивает единообразие представления чисел во всей системе.


Работа с некорректными значениями при округлении

Особое внимание требуется случаям:

  • пустые строки ""
  • null
  • undefined
  • строки с пробелами " "
transform((value, originalValue) => {
  if (originalValue == null || originalValue === '') {
    return value;
  }

  const parsed = Number(originalValue);

  return isNaN(parsed) ? value : Math.round(parsed * 100) / 100;
})

Игнорирование этих случаев может приводить к некорректной интерпретации данных.


Итоговое поведение числового округления в схемах

В рамках Yup округление всегда реализуется через преобразование входного значения перед применением правил проверки. Такой подход обеспечивает:

  • контроль над форматом числа
  • единообразие данных
  • совместимость с пользовательским вводом
  • возможность точной настройки логики округления

Механизм transform выступает центральным инструментом, позволяющим встроить арифметические преобразования непосредственно в схему валидации.