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


409

負の数に対して-1を返し、正の数に対して+1を返す関数が必要です。 http://en.wikipedia.org/wiki/Sign_function 自分で作成するのは簡単ですが、標準ライブラリのどこかにあるはずです。

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


13
0に対して何を返す必要がありますか?
クレイグマックイーン

61
@クレイグ・マックイーン; 正のゼロか負のゼロかによって異なります。
yth、2009

1
戻り値を整数として指定していることに気づきました。整数または浮動小数点数を取るソリューションをお探しですか?
Mark Byers、

6
@ysth @Craig McQueen、floatsについてもfalse、違いますか?sgn(x)の定義は、0の場合は0を返すとしていx==0ます。IEEE 754によれば、負のゼロと正のゼロは等しいものとして比較する必要があります。
RJFalconer 2014年

5
@ysth "正のゼロまたは負のゼロに依存します"。実際、そうではありません。
RJFalconer 2014

回答:


506

誰もまだタイプセーフなC ++バージョンを投稿していないことに驚いています。

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

利点:

  • 実際にsignum(-1、0、または1)を実装します。ここでcopysignを使用した実装は、-1または1のみを返しますが、これは符号ではありません。また、ここでのいくつかの実装は、intではなくfloat(またはT)を返すため、無駄が多いようです。
  • int、float、double、unsigned short、または整数0から構築可能で注文可能なカスタムタイプで機能します。
  • 速い!copysign特にプロモートしてから再び絞り込む必要がある場合は遅いです。これは分岐がなく、最適化します
  • 規格準拠!ビットシフトハックは巧妙ですが、一部のビット表現でのみ機能し、符号なしの型がある場合は機能しません。必要に応じて、手動の専門分野として提供できます。
  • 正確!ゼロとの単純な比較により、マシンの内部の高精度表現(x87の80ビットなど)を維持でき、ゼロへの早すぎる丸めを回避できます。

警告:

  • これはテンプレートなので、状況によってはコンパイルに時間がかかる場合があります。
  • どうやら、一部の人々は、signumを実際には実装していない、やや難解で非常に遅い標準ライブラリ関数の使用の方が理解しやすいと考えています。
  • < 0チェックの一部は、-Wtype-limits署名されていない型に対してインスタンス化されたときにGCCの警告をトリガーします。これを回避するには、いくつかのオーバーロードを使用します。

    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>());
    }
    

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


18
@GMan:たった今(4.5)GCCは、テンプレート関数のインスタンス化の数に対して2次のコストを持つことをやめました。それでも、手動で記述した関数や標準のCプリプロセッサよりも、構文解析とインスタンス化のコストが大幅に高くなります。リンカはまた、重複したインスタンス化を削除するために、より多くの作業を行う必要があります。テンプレートは#includes-in-#includesも奨励します。これにより、依存関係の計算により多くのファイルが再コンパイルされるように強制するために、長くて小さい(多くの場合、インターフェースではなく実装)変更が必要になります。

15
@ジョー:はい、そしてまだ顕著な費用はありません。C ++はテンプレートを使用します。これは、私たち全員が理解し、受け入れ、そして乗り越えなければならないものです。
GManNickG

42
待って、この「コピーサインは遅い」ビジネスとは...?(G ++ 4.6+、打ち鳴らす++ 3.0)現在のコンパイラを使用して、std::copysignになるように思われる優れた私のためのコード:4つの命令(インライン化)、分岐のない、完全にFPUを使用しました。対照的に、この回答で与えられたレシピは、はるかに悪いコードを生成します(乗算、整数ユニットとFPUの間を前後に移動するなど、より多くの命令)...
snogglethorpe

14
@snogglethorpe:copysignintを呼び出す場合、float / doubleに昇格し、戻り時に再び狭める必要があります。あなたのコンパイラはそのプロモーションを最適化するかもしれませんが、私はそれが標準によって保証されることを示唆するものを見つけることができません。また、copysignを介してsignumを実装するには、手動で0のケースを処理する必要があります。パフォーマンスの比較には必ずそれを含めてください。

53
最初のバージョンはブランチレスではありません。式で使用される比較では分岐が生成されないと思われるのはなぜですか?ほとんどのアーキテクチャで使用できます。cmove(または述語)を持つプロセッサのみがブランチレスコードを生成しますが、3項に対しても、それが勝利であればif / elseに対しても行います。
PatrickSchlüter12年

271

そのための標準的な関数は知りません。ここにそれを書く興味深い方法があります:

(x > 0) - (x < 0)

これを行うためのより読みやすい方法を次に示します。

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

三項演算子が好きなら、これを行うことができます:

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

7
マークランサム、あなたの表現はの不正な結果を与えますx==0
avakar 2009

3
@Svante:「指定された関係がtrueの場合、各演算子…は1を返し<>falseの場合は0を返します」
Stephen Canon

11
@Svante:正確ではありません。の値0は「false」です。その他の値はすべて「true」です。ただし、関係演算子と等価演算子は常に0or を返します1(Standard 6.5.8および6.5.9を参照)。-式の値はまたa * (x == 42)はのいずれ0aです。
pmg 2009

21
ハイパフォーマンスマーク、C ++タグを見逃したことに驚いています。この回答は非常に有効であり、反対票を投じる価値はありません。さらに、私はそれが利用可能であったとしてもcopysign、積分には使用しませxん。
avakar 2009

6
実際のプラットフォームでGCC / G ++ /その他のコンパイラーが発行するコードを実際に確認した人はいますか?私の推測では、「ブランチレス」バージョンは1つではなく2つのブランチを使用します。ビットシフトはおそらくはるかに高速です-パフォーマンスの点でより移植性があります。
ヨルゲンFogh

192

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

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を生成します。


57
これに賛成票を投じ、最も人気のある回答に反対票を投じました。SOコミュニティは、標準のライブラリ関数を使用するよりもハックを好むようであることに驚かされました。プログラミングの神々が、言語標準に不慣れな巧妙なプログラマが使用するハックを解読しようとすることをあなたにすべて非難しますように。ええ、これは私にSOの担当者のトンの費用がかかるだろうことを知っています、しかし私はあなたの他のメンバーよりも来る嵐の方をむしろしたいと思います...
ハイパフォーマンスマーク

34
これは近いですが、ゼロに対する間違った答えを与えます(少なくとも質問のWikipediaの記事によると)。素敵な提案ですが。とにかく+1。
Mark Byers、

4
整数が必要な場合、またはゼロの正確な符号結果が必要な場合は、非常にエレガントなMark Byersの回答が好きです。上記を気にしない場合、アプリケーションによってはcopysign()のパフォーマンスが向上する可能性があります。クリティカルループを最適化している場合は、両方を試してみます。
来年の嵐2009

10
1)C99はどこでも完全にサポートされているわけではありません(VC ++を検討してください); 2)これもC ++の質問です。これは良い答えですが、賛成投票も有効で、より広く適用できます。
Pavel Minaev 2009

5
救世主!-0.0と0.0の間で決定する方法が必要
アラフラワーゲ

79

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

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

標準ライブラリにはありませんが、をcopysign介してほぼ同じように使用できるものがありcopysign(1.0, arg)、には真の符号関数があり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


5
これは、質問での質問に最も近い解決策を提供するため、最も投票数の多い回答になるはずです。
BartoszKP 2015年

過去数分間、標準ライブラリに署名機能がないのかと思っていました。それは非常に一般的です-cmathヘッダーにあるガンマ関数よりも間違いなく一般的に使用されています。
Taozi

4
私がよく似た質問に対してよく受ける説明は、「自分で実装するのは簡単です」というIMOは正当な理由ではありません。それは、標準化の場所、明白でないエッジケース、およびそのような広く使用されているツールをどこに置くかという問題を完全に否定します。
Catskul 2016

77

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


2
@SR_不正解です。copysign()2番目が0.0の場合、最初のパラメータは0.0になりません。つまり、ジョンは正しいです。
Alexis Wilke

30

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

はい、定義によります。

C99以降にはsignbit()マクロが<math.h>

int signbit(リアルフローティングx); マクロ返す引数値の符号が負である場合にのみ非ゼロ値。C11§7.12.3.6
signbit


しかし、OPは少し違うものを求めています。

負の数に対して-1を返し、正の数に対して+1を返す関数が必要です。... floatを処理する関数。

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

もっと深く:

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

クラシックsignum()+1、オンx>0-1オンx<00オンに戻りますx==0

多くの回答がすでにそれをカバーしていますが、対処していませんx = -0.0, +NaN, -NaN。多くは、通常、非数(NaN)と-0.0が欠けている整数の視点に対応しています。

典型的な答えはsignnum_typical() Onのように機能し-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;
}

1
ああ、まさに私が求めているもの。これは、Pharo Smalltalk github.com/pharo-project/pharo/pull/1835で変更されたばかりで、負のゼロとナンの動作の動作を指示する何らかの標準(IEC 60559またはISO 10967)があるかどうか疑問に思いました...私は好きですJavaScriptの記号developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
aka.nice

29

上記のソリューションよりも高速で、最高評価のものも含まれます。

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

1
xはどのタイプですか?または、#defineを使用していますか?
チャンス

3
あなたのタイプは速くありません。キャッシュミスが頻繁に発生します。
ジェフリードレイク

19
キャッシュミス?方法がわかりません。多分あなたはブランチの予測ミスを意味しましたか?
Catskul 2013年

2
これにより、整数型とブール型の混乱を招く警告が表示されます。
sergiol

これはブランチでどのように速くなりますか?
Nick

16

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

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

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

そのページには、他にも興味深い、過度に賢いものがたくさんあります...


1
リンクを正しく読んだ場合、-1または0のみが返されます。-1、0、または+1が必要な場合は、sign = (v != 0) | -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));またはsign = (v > 0) - (v < 0);です。
Zボソン

1
これvは、それがintより広くない整数型であることを意味します
phuclv

12

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


7
sign(x)が必要な多くの数学的なアプリケーションがあります。それ以外の場合は、単に行いますif (x < 0)
チャンス

5

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

それとは別に、そのような関数を定義するための適切なアプローチに関する両方の多数の見方はある程度正しいと思います。2つの重要な注意点を考慮すると、それに関する「論争」は実際には議論の余地はありません。

  • シグナム関数は常に、同様に、そのオペランドの型を返すべきであるabs()ので、機能シグナムは、後者が何らかの形で処理された後、通常の絶対値との乗算のために使用されます。したがって、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)彼らは可能性が高い枝がないことをもだし、さらに浮動小数点への整数の後ろから結果をキャストする必要がないという理由だけで、移動するための方法です値。おそらく、浮動小数点のゼロ値の特殊性、処理時間の考慮事項、および正しい-1 / +を受け取る浮動小数点演算で非常に役立つため、signumの浮動小数点実装がゼロを返さないことをはっきりとコメントする必要があります。ゼロ値の場合でも1符号。


5

NutshellでのCの私のコピーは、便利かもしれないcopysignと呼ばれる標準関数の存在を明らかにします。copysign(1.0、-2.0)は-1.0を返し、copysign(1.0、2.0)は+1.0を返すように見えます。

かなり近いですか?


標準ではありませんが、広く入手できる場合があります。Microsoftは、アンダースコアで始まります。これは、非標準の拡張機能で使用される規則です。ただし、整数を操作する場合は最適ではありません。
Mark Ransom、

5
copysignは、ISO C(C99)とPOSIX標準の両方にあります。opengroup.org/onlinepubs/000095399/functions/copysign.htmlを
lhf 2009

3
lhfが言ったこと。Visual Studioは、C標準のリファレンスではありません。
スティーブンキャノン、

3

いいえ、MATLABのようにC ++には存在しません。このためにプログラムでマクロを使用しています。

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

5
C ++では、マクロよりもテンプレートを優先する必要があります。
ルスラン

Cでは、テンプレートはありません...... helloacm.com/how-to-implement-the-sgn-function-in-c
doctorlai Oct20

これは良い答えだと思ったので、自分のコードを見て、これを見つけました。#define sign(x) (((x) > 0) - ((x) < 0))これも良いことです。
ミシェルルージック

1
インライン関数はCのマクロよりも優れており、C ++テンプレートの方が優れています
phuclv

3

以下のオーバーロードで受け入れられた回答は、実際に-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 const x) {
    return T(0) < x;  
}

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

私にとっては、GCC 5.3.1で警告はトリガーされません。


-Wunused-parameter警告を回避するには、名前のないパラメーターを使用します。
Jonathan Wakely

それは実際には非常に本当です。私は逃しました。ただし、どちらの方法でも、C ++ 11の代替案の方が好きです。
SamVanDonut

2

少し話題外ですが、私はこれを使用します:

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));
}

そして私は最初の関数を見つけました-2つの引数を持つ関数は、「標準」のsgn()からはるかに役立つためです。

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

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

質問は古いですが、今はこの種の望ましい機能があります。not、left shift、decのラッパーを追加しました。

C99からのsignbitに基づくラッパー関数を使用して、正確な望ましい動作を得ることができます(以下のコードを参照)。

xの符号が負かどうかを返します。
これは、無限大、NaN、およびゼロにも適用できます(ゼロが符号なしの場合、正と見なされます)

#include <math.h>

int signValue(float a) {
    return ((!signbit(a)) << 1) - 1;
}

注:signbitの戻り値が1に指定されていないため(例では常にこのようになると考えています)、オペランドnot( "!")を使用していますが、負の数の場合はtrueです。

戻り値
xの符号が負の場合、ゼロ以外の値(true)。それ以外の場合はゼロ(false)。

次に、2を左シフト( "<< 1")で乗算します。これにより、正数の場合は2、負数の場合は0になり、最終的に1減分して、要求に応じて正数と負数のそれぞれ1と-1を取得します。 OP。


その場合、0も正になります...これは、OPが望んでいたことかもしれないし、そうでないかもしれません...
Antti Haapala

まあ、n = 0の場合、OPが本当に何を望んでいるかは決してわかりません...!
Antonin GAVREL

0

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

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

ハードコーディングされたとは対照的に、浮動小数点NANを返すことに注意NAN符号ビットがで設定されますいくつかの実装のために出力して、val = -NANそしてval = NANあなたが「好みならうとしていること(どんな同一ではないためにnan上の」出力を-nanあなたは置くことができますabs(val)リターンの前に...)



0

これはブランチフレンドリーな実装です:

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倍精度バイナリ浮動小数点形式であるbinary64で機能します。


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

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

  • 浮動小数点数のbinary32表現
  • 名前付きユニオンを使用するときに厳密なエイリアシング規則について例外を作成するコンパイラ

3
ここでもいくつかの悪い仮定があります。たとえば、floatのエンディアンが整数のエンディアンであることが保証されているとは思いません。また、ILP64を使用するすべてのアーキテクチャでチェックが失敗します。本当に、あなたは単に再実装しているだけですcopysign。使用しているstatic_assert場合はC ++ 11があり、実際にを使用することもできますcopysign


-3

簡単にできるのになぜ三項演算子とif-elseを使うのか

#define sgn(x) x==0 ? 0 : x/abs(x)

3
定義でも三項演算子を使用しています。
マーティンR

間違いなく、ただし、1つの3項演算子を使用してゼロと非ゼロの数値を分離するだけです。他のバージョンには、正、負、ゼロを分離する入れ子の3項演算が含まれています。
ジャグリット

整数除算の使用は非常に非効率的であり、abs()は整数専用です。
ミシェルルージック

ときに未定義の動作が発生する可能性がありx == INT_MINます。
chux-モニカの復活
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.