Avatar ·

Стандартная функция sign (signum, sgn) в C++: существует ли она и как её реализовать?

Привет, коллеги! Я пишу небольшую программу на C++ для численного анализа, и мне понадобилась функция, которая возвращает знак числа: -1 для отрицательных, 0 для нуля и 1 для положительных. В математике это называется signum или sgn. Я перерыл документацию по и , но не нашёл ничего похожего. В Python есть numpy.sign(), в MATLAB есть sign(), а в C++ — тишина. Неужели в стандартной библиотеке C++ нет такой элементарной вещи? Вот пример того, что я хочу сделать: ```cpp #include #include // может быть здесь? int main() { double x = -5.7; // Хочется написать что-то вроде: // int s = sign(x); // Но компилятор ругается, что sign не объявлен. // Приходится писать костыли: int s; if (x > 0) s = 1; else if (x < 0) s = -1; else s = 0; std::cout << "sign(" << x << ") = " << s << std::endl; return 0; } ``` Это работает, но выглядит ужасно. Для целых чисел я могу использовать трюк с (x > 0) - (x < 0), но для double это небезопасно из-за NaN. Подскажите, есть ли в C++ (C++11, C++17, C++20) стандартная функция для sign? Если нет, то какой самый правильный и эффективный способ её реализовать? Важно: нужно корректно обрабатывать NaN, signed zero и другие пограничные случаи. Заранее спасибо! <<>>

Краткий ответ: в стандартной библиотеке C++ нет функции sign, но есть элегантные решения

Вы правы — в C++98, C++11, C++17 и даже C++20 нет встроенной функции sign() или sgn(). Однако начиная с C++11 появились инструменты, которые позволяют реализовать её правильно и эффективно. Рассмотрим несколько подходов.

1. Классический подход с std::signbit (C++11+)

Стандартная функция std::signbit из определяет знак числа, включая корректную обработку отрицательного нуля (-0.0) и NaN. Комбинируя её с проверкой на ноль, получаем идеальную реализацию:

#include <cmath>   // для std::signbit, std::isnan
#include <type_traits>

template <typename T>
int sgn(T val) {
    static_assert(std::is_floating_point<T>::value ||
                  std::is_integral<T>::value,
                  "sgn() requires numeric type");
    
    if (std::isnan(val)) return 0;  // или можно вернуть val, зависит от задачи
    if (val == T(0)) return 0;
    return std::signbit(val) ? -1 : 1;
}

2. Современный C++20: std::strong_order и std::compare_three_way

В C++20 появился трёхсторонний оператор сравнения (spaceship operator) и функции сравнения с гарантией строгого порядка. Можно использовать std::strong_order из <compare>:

#include <compare>
#include <cmath>

template <std::floating_point T>
int sgn(T val) {
    if (std::isnan(val)) return 0;
    auto cmp = std::strong_order(val, T(0));
    if (cmp == std::strong_ordering::less) return -1;
    if (cmp == std::strong_ordering::greater) return 1;
    return 0;
}

3. Для целых чисел — оптимизированная версия

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

template <typename T>
int sgn_int(T val) {
    static_assert(std::is_integral<T>::value && std::is_signed<T>::value,
                  "sgn_int() requires signed integer type");
    return (T(0) < val) - (val < T(0));
}

Почему это лучший подход?

  • Корректность: правильно обрабатывает -0.0, NaN, бесконечности.
  • Производительность: компилятор оптимизирует код до одной-двух инструкций.
  • Безопасность типов: шаблоны с static_assert не дадут передать строку или указатель.

Замечание о NaN

В математике sign(NaN) не определён. В примерах выше я возвращаю 0, но вы можете изменить поведение: вернуть NaN, выбросить исключение или использовать std::optional<int>. Выбор зависит от контекста.

Итог: используйте шаблонную функцию с std::signbit для чисел с плавающей точкой и трюк с вычитанием для целых. Это стандартный идиоматичный подход в C++.

<<>>

Альтернатива: использование boost::math::sign()

Если вы не против внешних зависимостей, библиотека Boost предоставляет готовую функцию boost::math::sign() в заголовочном файле <boost/math/special_functions/sign.hpp>. Она уже решает все проблемы с NaN, signed zero и типами.

#include <boost/math/special_functions/sign.hpp>
#include <iostream>

int main() {
    double x = -5.7;
    int s = boost::math::sign(x);  // -1
    std::cout << s << std::endl;
    
    double nan_val = std::numeric_limits<double>::quiet_NaN();
    std::cout << boost::math::sign(nan_val);  // 0 (по умолчанию)
    return 0;
}

Плюсы: проверенное решение, поддержка всех пограничных случаев, документация.

Минусы: нужно подключать Boost, что увеличивает размер проекта и время сборки. Для небольших проектов проще написать свою реализацию из основного ответа.

Также обратите внимание: в Boost есть boost::math::copysign() для копирования знака одного числа на другое — может быть полезно в численных алгоритмах.