web-dev-qa-db-ja.com

C / C ++に標準の符号関数(signum、sgn)はありますか?

負の数の場合は-1、正の数の場合は+1を返す関数が必要です。 http://en.wikipedia.org/wiki/Sign_function 私自身で書くのは簡単ですが、それはあるべきもののようです。どこかの標準ライブラリ.

編集:具体的には、私はフロートで動作する機能を探していました。

370
batty

誰もまだブランチレスでタイプセーフなC++バージョンを投稿していません。

template <typename T> int sgn(T val) {
    return (T(0) < val) - (val < T(0));
}

利点:

  • 実際にsignum(-1、0、または1)を実装します。 copysignを使用したここでの実装では-1または1のみが返されます。これはsignumではありません。また、ここでの実装の中にはintではなくfloat(またはT)を返すものがあり、これは無駄に思えます。
  • Int、float、double、unsigned short、または整数0から構成可能で注文可能な任意のカスタム型に使用できます。
  • 速い! copysignは遅くなります。特に昇格してから絞り込む必要がある場合はなおさらです。これはブランチレスで、非常に最適化されています
  • 規格準拠!ビットシフトハックは巧妙ですが、いくつかのビット表現でしか機能せず、符号なしタイプの場合は機能しません。適切な場合は、手動の専門化として提供される可能性があります。
  • 正確!ゼロとの単純な比較は、機械の内部の高精度表現(例えば、x87では80ビット)を維持し、ゼロへの早すぎる丸めを避けることができます。

警告:

  • これはテンプレートなので、コンパイルには時間がかかります。
  • どうやら新しい、やや難解で非常に遅い標準ライブラリ関数の使用 それはsignumを実際には実装していない がより理解しやすいと思う人もいます。
  • 符号なしの型に対してインスタンス化されると、チェックの< 0部分はGCCの-Wtype-limits警告を引き起こします。いくつかのオーバーロードを使うことでこれを避けることができます。

    template <typename T> inline constexpr
    int signum(T x, std::false_type is_signed) {
        return T(0) < x;
    }
    
    template <typename T> inline constexpr
    int signum(T x, std::true_type is_signed) {
        return (T(0) < x) - (x < T(0));
    }
    
    template <typename T> inline constexpr
    int signum(T x) {
        return signum(x, std::is_signed<T>());
    }
    

    (これは最初の警告の良い例です。)

464
user79758

標準的な機能はわかりません。これを書くための面白い方法はここにあります:

(x > 0) - (x < 0)

これを行うためのもっと読みやすい方法は次のとおりです。

if (x > 0) return 1;
if (x < 0) return -1;
return 0;

あなたが三項演算子が好きなら、あなたはこれをすることができます:

(x > 0) ? 1 : ((x < 0) ? -1 : 0)
258
Mark Byers

一方の引数から符号を取り、もう一方の引数から絶対値を取る、copysign()と呼ばれるC99数学ライブラリ関数があります。

result = copysign(1.0, value) // double
result = copysignf(1.0, value) // float
result = copysignl(1.0, value) // long double

値の符号に応じて、+/ - 1.0の結果が得られます。浮動小数点ゼロに符号が付けられていることに注意してください。(+0)は+1になり、(-0)は-1になります。

181
comingstorm

どうやら、元のポスターの質問に対する答えはノーです。標準的な標準C++のsgn関数はありません。

73
John

ほとんどの回答は元の質問を逃したようです。

C/C++に標準の符号関数(signum、sgn)はありますか?

標準ライブラリにはありませんが、copysign(1.0, arg)とほぼ同じように使用できる copysign があり、 boost には真の符号関数があります。これも標準の一部かもしれません。

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

    //Returns 1 if x > 0, -1 if x < 0, and 0 if x is zero.
    template <class T>
    inline int sign (const T& z);

http://www.boost.org/doc/libs/1_47_0/libs/math/doc/sf_and_dist/html/math_toolkit/utils/sign_functions.html

72
Catskul

最も高い評価のものを含む上記の解決策よりも速いです:

(x < 0) ? -1 : (x > 0)
28
xnx

C/C++に標準の符号関数(signum、sgn)はありますか?

はい、定義次第です。

C99以降の<math.h>signbit()マクロがあります。

int signbit(real-floating x);
signbitマクロは、引数値の符号が負の場合に限り、ゼロ以外の値を返します。 C11§7.12.3.6


それでもOPは少し違うものを望んでいます。

負の数の場合は-1、正の数の場合は+1を返す関数が必要です。 ...フロートを扱う機能.

#define signbit_p1_or_n1(x)  ((signbit(x) ?  -1 : 1)

より深く:

投稿は、x = 0.0, -0.0, +NaN, -NaNの場合には特定されません。

古典的な signum() は、+1x>0-1x>00x==0を返します。

多くの答えがすでにそれをカバーしていますが、x = -0.0, +NaN, -NaNを扱っていません。多くの場合、整数ではない数字( NaN )や が通常使用されます。 -0.0 .

典型的な答えはsignnum_typical()のように機能します-0.0, +NaN, -NaNでは、それらは0.0, 0.0, 0.0を返します。

int signnum_typical(double x) {
  if (x > 0.0) return 1;
  if (x < 0.0) return -1;
  return 0;
}

代わりに、この機能を提案してください。-0.0, +NaN, -NaNでは、-0.0, +NaN, -NaNを返します。

double signnum_c(double x) {
  if (x > 0.0) return 1.0;
  if (x < 0.0) return -1.0;
  return x;
}
21
chux

分岐せずにそれを行う方法がありますが、それは非常にきれいではありません。

sign = -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));

http://graphics.stanford.edu/~seander/bithacks.html

そのページには、他にも面白くて賢いものがたくさんあります。

16
Tim Sylvester

符号をテストするだけでよい場合は、 signbit を使用します(引数に負の符号がある場合はtrueを返します)。特に-1または+1を返したい理由がわからない。 copysignの方が便利ですが、一部のプラットフォームでは負のゼロを部分的にしかサポートしていないため、signbitがおそらくtrueを返すような場合に、負のゼロに+1を返すように思われます。

11
ysth

一般に、C/C++には標準のsignum関数はなく、そのような基本的な関数がないことから、これらの言語について多くのことがわかります。

それとは別に、そのような関数を定義するための正しいアプローチについての両方の大多数の見解はある意味で正しいと考えています。2つの重要な警告を考慮に入れると、それに関する「論争」は実際には議論の余地がありません。

  • signum関数は、abs()関数と同様に、常にそのオペランドの型を返す必要があります。なぜなら、signumは通常、絶対値が何らかの形で処理された後の絶対値との乗算に使用されます。したがって、signumの主な使用例は比較ではなく算術演算であり、後者には高価な整数から浮動小数点への変換は含まれません。コンバージョン.

  • 浮動小数点型は単一の正確なゼロ値を特徴としていません:+0.0は "ゼロより上の無限小"として、-0.0は "ゼロ未満の無限小"として解釈することができます。これが、ゼロを含む比較が両方の値に対して内部的にチェックしなければならない理由であり、x == 0.0のような式は危険な場合があります。

Cに関して言えば、整数型を使う上で最善の方法は、(x > 0) - (x < 0)式を使うことだと思います。なぜなら、それはブランチフリーな方法で翻訳されるべきで、3つの基本操作しか必要としないからです。引数の型と一致する戻り型を強制するインライン関数を定義し、これらの関数を共通の名前にマップするためのC11 define _Genericを追加することをお勧めします。

浮動小数点値では、C11のcopysignf(1.0f, x)copysign(1.0, x)、およびcopysignl(1.0l, x)に基づくインライン関数は、それらがブランチフリーである可能性が非常に高く、さらに整数からの結果をにキャストバックする必要がないという理由で進むべき道だと思います浮動小数点値signumの浮動小数点実装は、浮動小数点ゼロ値の特殊性、処理時間の考慮事項、および浮動小数点演算では、ゼロの値に対しても正しい-1/+ 1符号を受け取ることが非常に便利であるためです。

5
Tabernakel

私の一言で言えばCは、copysignという標準関数が存在することを示しています。 copysign(1.0、-2.0)は-1.0を返し、copysign(1.0、2.0)は+1.0を返すように見えます。

かなり近いでしょ?

下記の過負荷で受け入れられた答えは確かに - Wtype-limitsを引き起こさない。

template <typename T> inline constexpr
  int signum(T x, std::false_type) {
  return T(0) < x;
}

template <typename T> inline constexpr
  int signum(T x, std::true_type) {
  return (T(0) < x) - (x < T(0));
}

template <typename T> inline constexpr
  int signum(T x) {
  return signum(x, std::is_signed<T>());
}

C++ 11の場合、代替方法があります。

template <typename T>
typename std::enable_if<std::is_unsigned<T>::value, int>::type
inline constexpr signum(T x) {
    return T(0) < x;  
}

template <typename T>
typename std::enable_if<std::is_signed<T>::value, int>::type
inline constexpr signum(T x) {
    return (T(0) < x) - (x < T(0));  
}

私にとっては、GCC 5.3.1に関する警告は表示されません。

3
SamVanDonut

いいえ、それはmatlabのようにc ++には存在しません。これにはプログラムでマクロを使用します。

#define sign(a) ( ( (a) < 0 )  ?  -1   : ( (a) > 0 ) )
3
chattering

Boostが利用可能であれば、boost/math/special_functions/sign.hppからboost::math::sign()メソッドを使うことができます。

1
khkarens

ちょっと話題にはなりませんが、私はこれを使用します。

template<typename T>
constexpr int sgn(const T &a, const T &b) noexcept{
    return (a > b) - (a < b);
}

template<typename T>
constexpr int sgn(const T &a) noexcept{
    return sgn(a, T(0));
}

そして私は最初の関数 - 二つの引数を持つもの - が "標準的な" sgn()からもっと有用であることを見出しました。

int comp(unsigned a, unsigned b){
   return sgn( int(a) - int(b) );
}

vs.

int comp(unsigned a, unsigned b){
   return sgn(a, b);
}

符号なし型へのキャストや追加のマイナスはありません。

実際、私はsgn()を使用してこのコードを持っています

template <class T>
int comp(const T &a, const T &b){
    log__("all");
    if (a < b)
        return -1;

    if (a > b)
        return +1;

    return 0;
}

inline int comp(int const a, int const b){
    log__("int");
    return a - b;
}

inline int comp(long int const a, long int const b){
    log__("long");
    return sgn(a, b);
}
1
Nick

これは分岐にやさしい実装です。

inline int signum(const double x) {
    if(x == 0) return 0;
    return (1 - (static_cast<int>((*reinterpret_cast<const uint64_t*>(&x)) >> 63) << 1));
}

あなたのデータが数の半分としてゼロを持たない限り、ここで分岐予測子は最も一般的なものとして分岐の1つを選びます。どちらの分岐も単純な操作のみを含みます。

あるいは、一部のコンパイラやCPUアーキテクチャでは、完全にブランチのないバージョンが速いかもしれません。

inline int signum(const double x) {
    return (x != 0) * 
        (1 - (static_cast<int>((*reinterpret_cast<const uint64_t*>(&x)) >> 63) << 1));
}

これは、 IEEE 754倍精度2進浮動小数点形式、binary64 で機能します。

0
Serge Rogatch

受け入れられた答えの整数解は非常に洗練されていますが、double型に対してNANを返すことができないことを私に悩ませたので、私はそれをわずかに修正しました。

template <typename T> double sgn(T val) {
    return double((T(0) < val) - (val < T(0)))/(val == val);
}

ハードコードされたNANではなく浮動小数点NANを返すと、符号ビットが いくつかの実装 に設定されるので、val = -NANval = NANの出力何に関係なく同一になるでしょう(-nanよりも "nan"の出力を好む場合は、リターンの前にabs(val)を置くことができます...)

0
mrclng
int sign(float n)
{     
  union { float f; std::uint32_t i; } u { n };
  return 1 - ((u.i >> 31) << 1);
}

この関数は、次のことを前提としています。

  • 浮動小数点数のbinary32表現
  • 名前付き共用体を使用するときに厳密なエイリアシングに関する例外規則を作成するコンパイラ
0
Gigi