Работа с числами в JavaScript неизбежно сталкивается с особенностями
IEEE-754 представления, где дробные значения часто теряют ожидаемую
точность. В контексте валидации данных это приводит к необходимости явно
ограничивать и контролировать количество знаков после запятой, особенно
в финансовых, научных и аналитических данных. В Joi для этих целей
используется механизм ограничения точности через метод
precision.
Числа с плавающей точкой в JavaScript не являются точными в математическом смысле. Например:
0.1 + 0.2 === 0.3 // false
Результат выражения оказывается false из-за особенностей
двоичного представления дробей. Валидационные схемы должны учитывать
подобные погрешности, особенно при проверке входных данных, где
требуется фиксированное количество знаков после запятой.
В библиотеке Joi метод precision применяется к числовым
схемам и задаёт максимально допустимое количество знаков после
десятичной точки.
Сигнатура:
Joi.number().precision(limit)
где limit — целое число, определяющее максимальную
точность.
При использовании precision Joi проверяет количество
знаков после запятой и отклоняет значения, превышающие установленный
лимит.
const schema = Joi.number().precision(2);
schema.validate(10.12); // корректно
schema.validate(10.123); // ошибка валидации
В данном случае значение 10.123 содержит три знака после
запятой и нарушает ограничение.
При нарушении ограничения точности Joi возвращает структурированное сообщение об ошибке:
{
message: '"value" must have no more than 2 decimal places',
path: [],
type: 'number.precision',
context: {
limit: 2,
value: 10.123
}
}
Тип ошибки number.precision позволяет однозначно
идентифицировать причину отклонения значения.
Joi может автоматически приводить строки к числам при включённой опции преобразования:
Joi.number().precision(2).validate("10.10");
Строка "10.10" будет преобразована в число
10.1, после чего проверка точности выполнится уже над
числовым значением. Это важно учитывать при работе с пользовательским
вводом, где данные часто приходят в строковом формате.
Целые числа не нарушают ограничение точности:
const schema = Joi.number().precision(3);
schema.validate(5); // допустимо
schema.validate(100); // допустимо
Внутренне такие значения не имеют дробной части, поэтому ограничение
считается соблюдённым независимо от параметра
precision.
Joi не выполняет автоматическое округление значений при превышении точности. Значение либо проходит валидацию, либо отклоняется.
Joi.number().precision(2).validate(10.129);
Результат — ошибка, а не преобразование в 10.13. Это
принципиальное поведение, позволяющее избежать скрытых модификаций
входных данных.
Метод precision часто используется вместе с другими
числовыми правилами:
const schema = Joi.number()
.min(0)
.max(100)
.precision(2);
Такая схема ограничивает диапазон значений и одновременно фиксирует формат представления дробной части.
JavaScript ограничивает безопасные целые числа диапазоном
Number.MAX_SAFE_INTEGER. Ограничение точности не влияет на
целую часть, но может сочетаться с проверкой:
Joi.number()
.integer()
.max(Number.MAX_SAFE_INTEGER);
Если одновременно применяется precision, правило
integer фактически делает дробную часть недопустимой, и
precision становится избыточным.
Значения NaN, Infinity и
-Infinity не проходят числовую валидацию независимо от
настройки точности:
Joi.number().precision(2).validate(NaN); // ошибка
Joi.number().precision(2).validate(Infinity); // ошибка
Метод precision применяется только к конечным числовым
значениям с фиксированной десятичной частью.
Ограничение точности одинаково применяется к отрицательным значениям:
Joi.number().precision(2).validate(-10.12); // допустимо
Joi.number().precision(2).validate(-10.123); // ошибка
Знак числа не влияет на подсчёт количества знаков после запятой.
Числа могут поступать в разных формах записи:
Пример:
Joi.number().precision(2).validate(1e-2);
Значение 1e-2 интерпретируется как 0.01, и
точность считается корректной. Однако при экспоненциальной записи
внутреннее преобразование может влиять на итоговое количество значащих
цифр, что требует осторожности при работе с научными данными.
В строгом режиме Joi отключает автоматические преобразования типов:
const schema = Joi.number().precision(2).strict();
В этом случае строковые значения даже корректного числового вида не приводятся к числу, а сразу отклоняются:
schema.validate("10.10"); // ошибка
schema.validate(10.10); // корректно
Это усиливает контроль над точностью входных данных.
Проверка precision основана на анализе десятичного
представления числа. Joi определяет количество цифр после точки путём
преобразования числа в строку и анализа дробной части.
При этом учитываются особенности плавающей арифметики, поэтому возможны ситуации:
10.1 // может быть представлено как 10.0999999999999
Для таких случаев Joi нормализует значение перед проверкой, минимизируя влияние машинного представления числа.
Несмотря на удобство, метод имеет ряд ограничений:
В задачах, где критична абсолютная точность (например, валютные операции), часто применяются подходы с использованием целых чисел (копейки вместо рублей) или decimal-библиотек.
При необходимости более сложной логики точности используется
расширение через custom:
Joi.number().custom((value, helpers) => {
const decimalPart = value.toString().split('.')[1];
if (decimalPart && decimalPart.length > 2) {
return helpers.error('number.precision');
}
return value;
});
Такой подход позволяет учитывать бизнес-правила, выходящие за рамки
стандартного precision.
При последующей сериализации валидированных данных точность может
изменяться из-за особенностей JSON.stringify и внутреннего
представления числа:
JSON.stringify(10.1); // "10.1"
JSON.stringify(10.10); // "10.1"
Таким образом, precision влияет только на этап
валидации, но не фиксирует формат хранения данных.
Механизм точности в Joi представляет собой инструмент контроля формата числовых данных, ориентированный на проверку структуры значения, а не на математическую нормализацию вычислений.