Работа с отрицательными числами

При работе с денежными значениями, бухгалтерскими системами, финансовыми панелями, аналитикой и пользовательскими формами возникает необходимость корректно обрабатывать отрицательные числа. Библиотека Cleave.js поддерживает подобные сценарии через механизм числового форматирования.

Отрицательные значения особенно важны в следующих случаях:

  • финансовые убытки;
  • списания средств;
  • корректировки баланса;
  • температурные значения;
  • аналитические коэффициенты;
  • расчёты со знаком;
  • изменения показателей относительно предыдущего периода.

Главная особенность заключается в том, что отрицательное число содержит символ -, который должен корректно взаимодействовать с:

  • разделителями тысяч;
  • десятичными разделителями;
  • ограничениями длины;
  • локализацией;
  • кареткой ввода;
  • пользовательским редактированием.

Базовая поддержка отрицательных чисел

Для числового режима используется параметр numeral.

<input id="price">
new Cleave('#price', {
    numeral: true
});

Поле начинает автоматически форматировать числа:

1000      → 1,000
5000000   → 5,000,000

По умолчанию отрицательные числа также поддерживаются:

-1000     → -1,000
-55000    → -55,000

Символ минуса сохраняется в начале строки и участвует в дальнейшем форматировании.


Параметр numeralPositiveOnly

Для управления отрицательными значениями существует специальная настройка:

numeralPositiveOnly

Она определяет, разрешены ли отрицательные числа.

Разрешение отрицательных значений

new Cleave('#price', {
    numeral: true,
    numeralPositiveOnly: false
});

Теперь допустимы:

-10
-2500
-999999

Запрет отрицательных значений

new Cleave('#price', {
    numeral: true,
    numeralPositiveOnly: true
});

В этом режиме:

-1000

автоматически преобразуется в:

1000

или минус игнорируется ещё на этапе ввода.


Поведение при вводе минуса

Минус в начале строки

Корректный сценарий:

-12345

После форматирования:

-12,345

Минус в середине строки

Некорректный ввод:

123-45

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

Несколько минусов

Ввод:

--100

обычно преобразуется к:

-100

Лишние символы удаляются.


Работа с decimal и отрицательными значениями

Отрицательные числа часто используются вместе с дробной частью.

new Cleave('#price', {
    numeral: true,
    numeralDecimalScale: 2
});

Пример:

-1234.56

После форматирования:

-1,234.56

Настройка десятичного разделителя

Европейский формат

new Cleave('#price', {
    numeral: true,
    numeralDecimalMark: ',',
    delimiter: ' '
});

Ввод:

-1234567,89

Результат:

-1 234 567,89

Форматирование валют с отрицательными значениями

Отрицательные суммы

new Cleave('#price', {
    numeral: true,
    prefix: '$',
    rawValueTrimPrefix: true
});

Результат:

-$5,000

или:

$-5,000

в зависимости от логики обработки строки.


Управление позицией минуса

В большинстве финансовых систем используются два основных варианта отображения.

Минус перед префиксом

-$1,500

Минус после префикса

$-1,500

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

Пример ручной корректировки:

const cleave = new Cleave('#price', {
    numeral: true,
    prefix: '$',
    rawValueTrimPrefix: true
});

document.querySelector('#price').addEventListener('input', function () {
    let value = cleave.getRawValue();

    if (value.startsWith('-')) {
        this.value = '-$' + value.substring(1);
    }
});

Получение отрицательного значения через getRawValue

Метод:

getRawValue()

возвращает исходное значение без форматирования.

Пример:

-1,234,567.89

Метод вернёт:

-1234567.89

Это особенно важно для:

  • отправки данных на сервер;
  • математических расчётов;
  • API-запросов;
  • финансовой логики;
  • хранения в базе данных.

Конвертация в Number

Чаще всего после получения raw-значения выполняется преобразование:

const raw = cleave.getRawValue();
const number = Number(raw);

Для отрицательных значений:

"-5500.25"

результат:

-5500.25

Ограничение диапазона отрицательных чисел

Проверка минимального значения

const input = document.querySelector('#price');

input.addEventListener('input', function () {
    const value = Number(cleave.getRawValue());

    if (value < -1000) {
        console.log('Минимальный предел превышен');
    }
});

Ограничение диапазона с обеих сторон

input.addEventListener('input', function () {
    const value = Number(cleave.getRawValue());

    if (value < -5000 || value > 5000) {
        console.log('Число вне диапазона');
    }
});

Автоматическое исправление значения

input.addEventListener('input', function () {
    let value = Number(cleave.getRawValue());

    if (value < -1000) {
        value = -1000;
    }

    if (value > 1000) {
        value = 1000;
    }

    this.value = value;
});

Работа с бухгалтерским форматом

В некоторых системах отрицательные значения отображаются через круглые скобки.

Пример бухгалтерского отображения

(1,500.00)

Внутри Cleave.js подобное форматирование отсутствует, однако его можно реализовать вручную.

function formatAccounting(value) {
    const number = Number(value);

    if (number < 0) {
        return '(' + Math.abs(number).toLocaleString() + ')';
    }

    return number.toLocaleString();
}

Отрицательные проценты

Пример

new Cleave('#percent', {
    numeral: true,
    suffix: '%'
});

Ввод:

-12.5

Результат:

-12.5%

Подобный формат часто применяется в:

  • аналитике;
  • инвестиционных панелях;
  • биржевых интерфейсах;
  • статистике;
  • отчётах.

Отрицательные температуры

new Cleave('#temp', {
    numeral: true,
    suffix: ' °C'
});

Результат:

-15 °C

Валидация отрицательных чисел

Проверка через RegExp

const regex = /^-?\d+(\.\d+)?$/;

Поддерживаются:

100
-100
55.25
-55.25

Проверка NaN

const value = Number(cleave.getRawValue());

if (Number.isNaN(value)) {
    console.log('Некорректное число');
}

Обработка пустого минуса

Промежуточное состояние ввода:

-

является распространённой проблемой.

Пользователь может ввести минус, но ещё не указать цифры.

Возможная проверка

const raw = cleave.getRawValue();

if (raw === '-') {
    return;
}

Без подобной проверки:

Number('-')

вернёт:

NaN

Работа с React

Пример компонента

import Cleave from 'cleave.js/react';

function PriceInput() {
    return (
        <Cleave
            options={{
                numeral: true,
                numeralDecimalScale: 2,
                numeralPositiveOnly: false
            }}
        />
    );
}

Работа с Vue

<cleave
    :options="options"
    v-model="price"
/>
export default {
    data() {
        return {
            price: '',
            options: {
                numeral: true,
                numeralPositiveOnly: false
            }
        };
    }
};

Работа с Angular

<input
    cleave="{
        numeral: true,
        numeralPositiveOnly: false
    }"
/>

Типичные проблемы отрицательных чисел

Потеря минуса при переформатировании

Причины:

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

Ошибочный parseInt

parseInt('-12.99')

вернёт:

-12

Дробная часть будет потеряна.

Для финансовых значений обычно используется:

parseFloat()

или:

Number()

Проблемы с локализацией

Некоторые локали используют:

-1.234,56

а другие:

-1,234.56

Неправильная конфигурация:

numeralDecimalMark
delimiter

может привести к ошибкам парсинга.


Использование custom delimiter с отрицательными числами

new Cleave('#price', {
    numeral: true,
    delimiter: '_'
});

Результат:

-1_000_000

Ограничение количества знаков

new Cleave('#price', {
    numeral: true,
    numeralIntegerScale: 6
});

Максимально допустимое значение:

999999

Для отрицательных чисел:

-999999

Минус не учитывается в ограничении длины целой части.


Использование numeralThousandsGroupStyle

Тысячные группы lakh

new Cleave('#price', {
    numeral: true,
    numeralThousandsGroupStyle: 'lakh'
});

Результат:

-12,34,567

Тысячные группы wan

new Cleave('#price', {
    numeral: true,
    numeralThousandsGroupStyle: 'wan'
});

Результат:

-1234,5678

Использование sign-aware логики

Иногда необходимо отдельно обрабатывать знак числа.

Определение знака

const value = Number(cleave.getRawValue());

if (value < 0) {
    console.log('Отрицательное число');
}

Получение абсолютного значения

Math.abs(value)

Финансовые сценарии

Баланс счёта

-15,000

может обозначать задолженность.

Изменение котировок

-2.15%

обозначает снижение.

Расходы

-$550

используется для отображения списаний.


Динамическое изменение цвета

Отрицательные значения часто выделяются визуально.

input.addEventListener('input', function () {
    const value = Number(cleave.getRawValue());

    if (value < 0) {
        this.style.color = 'red';
    } else {
        this.style.color = 'black';
    }
});

Работа с сервером

Перед отправкой данных рекомендуется:

  1. Получить raw-значение.
  2. Преобразовать строку в Number.
  3. Выполнить валидацию диапазона.
  4. Проверить NaN.
  5. Проверить бизнес-ограничения.

Пример

const raw = cleave.getRawValue();
const value = Number(raw);

if (!Number.isNaN(value)) {
    sendToServer(value);
}

Практические рекомендации

Для финансовых интерфейсов

Обычно используются:

numeral: true
numeralDecimalScale: 2
numeralPositiveOnly: false

Для температур

Часто достаточно:

numeral: true
numeralPositiveOnly: false

Для форм с ограничением

Дополнительно применяются:

  • min/max проверки;
  • пользовательская валидация;
  • автоматическое исправление;
  • визуальное выделение ошибок.

Особенности UX при работе с отрицательными числами

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

  • минус вводится только в начале;
  • автоматически удаляются лишние минусы;
  • корректно работает backspace;
  • каретка не прыгает при форматировании;
  • дробная часть сохраняется;
  • локализация не ломает формат;
  • raw-значение всегда остаётся валидным числом.