こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

if文を使わずに小数の符号を取れるか

if文を使わずに、値が正なら1、0なら0、負なら-1を出力する方法はありますか?

if文(とできれば掛け算)を使わずに、変数a(1か-1が入る)と変数b(小数が入る)の符号が違ったらカウントを増やすプログラムを作らないといけないのですが、
変数bを1,0,-1に分けることができれば楽になると考えたのですが、
符号を取り出す関数を見つけることができませんでした。
b/fabs(b)も考えたのですが、これだとb=0の場合に0/0となるためうまくいきません。

投稿日時 - 2009-12-14 06:52:44

QNo.5521178

すぐに回答ほしいです

質問者が選んだベストアンサー

No.6 です。
よく見たら、値の制限がかなり強いのですね。まあ、それなら、それ用の手もありかもしれません。

int cntGT[3] = {0};
int cntEQ[3] = {0};
int cntAll = 0;

cntGT[a + 1] += (b > 0);
cntEQ[a + 1] += (b == 0);
cntAll ++;

ここで最終的に
cntEQ[0] + cntEQ[2] が、b == 0 の数
cntGT[0] が、a == -1 で、b > 0 の数
cntGT[2] が、a == 1 で、b > 0 の数

だから、処理が終わった後で、

cntEQ[0] + cntEQ[2] (ここまで b == 0)
+ cnt[0] + (cntAll - cnGT[2]) (ここまで、b != 0 で a b が異符号)

で合計が出るような気がする。
3つもカウントアップするのがいやかもしれません。

投稿日時 - 2009-12-14 13:24:57

このQ&Aは役に立ちましたか?

7人が「このQ&Aが役に立った」と投票しています

回答(20)

ANo.20

No.19です。
よく考えたらそれぞれビットシフトする必要はありませんでしたね。
よって
i = i + (((int)b ^ a) >> 15);
の方がちょっと早くなるかもしれませんね。

投稿日時 - 2009-12-14 19:48:00

ANo.19

>なるほど、ビットシフトを使えば処理を速くできそうです。
>変数aはint型なので15ビット右にシフトすればよさそうですが、
>double型の変数bは何ビットシフトすればいいのでしょうか。

(int)b

コレでintに変換できます。
-0.3とかが入っていたら0になったかもしれませんので、その場合は
(int)(b-1)
とすればa.bどちらもint型となり、負の場合は最上位ビットが1になります。

具体的には
i = i + ((int)b >> 15) ^ (a >> 15);
コンパイルしてないので動くか分かりませんが、こんな感じで良いと思います。

投稿日時 - 2009-12-14 19:27:50

ANo.18

No.13です。
>授業の課題として、データを大量に集めて確率などを求めるためのプログラムを作っているので、
>数百、数千万回、またはそれ以上繰り返すのが前提になっています。
では、別のアプローチの解も書いておきましょう。

「地球シミュレータ」の有償利用サービスの拡大について
http://www.jamstec.go.jp/j/about/press_release/20070614/
成果の占有を前提とした場合でも、一時間4000円程度で借りれます。
これなら、ちょっとやそっとのコード増加なんて屁でもない。
なにせ、過去には「世界一」もとったことのある後継機なのだから。

という冗談(あながち冗談てはないけど)はさておき、
個人的にいえば、
全体から見たら(言い方は悪いけど)ゴミみたいな部分に神経使うよりは、まずは設計されたものを「設計通り」に作りこむことに注力すべきだと思う。
この時、後の修正や変更が楽になるように「見やすく・わかりやすく」作りこむことも重要。
経験からいえば、些細な処理の書き方一つより、統合した全体として遅延問題を誘発する可能性の方がはるかに大きい。
同じことを触れられている方がいるけど、
そのif文の処理は「データを収集し確立を計算する1回の処理」に対してどれだけの処理時間を占めるの?
「チリも積もれば……」も真実の一つだけど、返答から見るとそれだけで解決できる問題ではないと思う。

投稿日時 - 2009-12-14 19:15:55

ANo.17

No.12の回答者です。
実際に測ってみればということで
(1)No.12に示した関数
(2)
inline int signf( double x ){
if ( x<0.0 ) return -1;
else if ( x>0.0 ) return 1;
else return 0;
}
を関数
void signfa( double x[], int y[], const int n) {
for ( int i=0; i<n; i++ ) y[i]=signf( x[i] );
}
から呼んで所要時間を計りました。
Visual Studio 2008のQueryPerformanceCounter()を使い、n=10,000からn=10,000,000までx[]には正負ほぼ同数の乱数(値0もわずかに含まれています)を与えました。
release ビルドで実行させたところ、(1)と(2)の所要時間の比はnによらずおおむね1:1.2程度で、(1)のほうが短時間で処理できました。
n=10,000,000のときの所要時間は(1)、(2)それぞれ、0.0780secと0.0952secでした。

投稿日時 - 2009-12-14 17:56:38

ANo.16

とりあえず質問に答えます。

> if文を使わずに、値が正なら1、0なら0、負なら-1を出力する方法はありますか?

(b > 0) - (b < 0)

とすれば、bの型やサイズや内部表現に関わらず、望みの結果が得られます。
マクロや関数テンプレートにでもしておくと、使い回しが効くので便利でしょう。

aとbの符号が異なるときにカウントアップということであれば、

count += (a != ((b > 0) - (b < 0)));

で実現できます。

次にif文を使いたくない状況ですが、少なからず存在します。
最も典型的なのは結果を定数式として欲しい場合です。初期化子やcaseラベル、配列の要素数に使いたい場合のほか、テンプレート実引数に使う場合にも有用です。ジェネリックプログラミングには必須といえるでしょう。
また、コンパイラの最適化性能は処理系によってかなり異なります。極端なことをいえば、インタープリタ環境では、バイトコードコンパイルの有無程度が精一杯でそれ以上の最適化は期待できないことことも多々あります。最適化バグを回避するために、そのそも最適化を抑止しなければならない状況もあります。

投稿日時 - 2009-12-14 17:28:26

ANo.15

No.6 です。

> if(a*b<=0)count++ とかでいいのでしょうか。

それでいい可能性が非常に高いです。
あと、これだと、誰が読んでも意図がわかります。

実は、もうひとつの観点があって、たとえば、
「a と b の符号が異なるときだけカウントアップ」という処理が、数万回あるとして、「この処理だけが数万回ある」ということは、ほぼあり得ないということです。

少なくとも、ループのための処理はあるでしょうし、数万個のデータが計算されるか、ファイルから読まれるかするはずです。
その「ある程度の塊」が、数万回実行されることになるのが普通なのですが、さて、その中で、比較や、浮動小数点の乗算がどの程度の時間を占めているかという観点です。

全体の1%を占めていたとして、処理時間が10倍になったとしたら、全体の処理時間は、10%増加します。
どちらも、かなり極端な仮定だと思いますが、それでも、10%増加するだけです。

10秒かかる処理が11秒になったら困りますか?
もしも、10日かかる処理が、11日かかるとしたら、10日もかかるのが正しいのかということを疑うべきです(これが、アルゴリズムの検討)
というわけで、こういう処理というのは、わかりやすく書いておくのがベストです。

投稿日時 - 2009-12-14 17:25:04

ANo.14

数千万回「程度」で気になるほど違いが出るものなのかなぁ. 正直なところ, 「とりあえず何も考えずに実装して, どうしても気になるようならそのときになって初めて取り組む」くらいで十分だと思う.
まあ, 「どのように書いたら速いのか」というのはコンパイラとかプロセッサにも依存するんだけどね.

投稿日時 - 2009-12-14 17:14:17

ANo.13

「条件分岐」としての処理でいえば、「if」も「三項演算子」もそれほど違いはないので、単純に「ifを使ってない」という言葉に惑わされないように。

>数百、数千万回とループさせる場合、if文や掛け算は処理に時間がかかるからなるべく使うな、
>この部分も処理を軽くするためにif文を使わないで書け、と言われたのです。
FPGAなんかでは今でも通用する事象ですが、
μオーダーでのシピアな制御タイミングが伴わないPCアプリなら気にするほどのことでもありません。
それよりも
>数百、数千万回とループさせる
という処理の方が問題ありの気がしますが。

投稿日時 - 2009-12-14 15:35:34

補足

>>数百、数千万回とループさせる
>という処理の方が問題ありの気がしますが。
授業の課題として、データを大量に集めて確率などを求めるためのプログラムを作っているので、
数百、数千万回、またはそれ以上繰り返すのが前提になっています。

投稿日時 - 2009-12-14 16:47:01

ANo.12

こういうのは頭の体操として考えるのが…
double型とlong long int型とが同じサイズで、最上位ビットが符号であれば、これでどうでしょうか。
inline int signf( double x ){
/* programmed by Akayoroshi on 2009-12-14. */
double y=0.0-x;
long long int *ix=(long long int*)&x;
long long int *iy=(long long int*)&y;
return (*ix>>63)-(*iy>>63);
}
Visual Studio 2008では、プログラムの3行目を、double y=-xとしたらyの値が-0.0になって関数の戻り値は-1になってしまいました。

投稿日時 - 2009-12-14 15:10:15

ANo.11

あるいは、
if( a < 0 ){
if( b <= 0 )cnt++;
}else{
if( b >= 0 )cnt++;
}
こうかな。

投稿日時 - 2009-12-14 14:57:38

ANo.10

>bが0のときはカウントを増やさないといけません。
bが0のときは、aの値とは無関係にカウント増やすということでしょうか?

だとすれは、おそらくは単純に、
if( ( b == 0 )|| ((a<0)&&(b<0)) || ((a>0)&&(b>0)) )cnt++;
で、十分早いと思います。
これ以上はアセンブリ出力と実際に測定した結果を見ながらカリカリやるしかないですね。
#aおよびbの符号の出現確率によって、条件式の順番を入れ替えることは効果があるかと思います。

投稿日時 - 2009-12-14 14:37:43

ANo.9

訂正です。
まあ、わかるとは思いますが……。

((b == 0) && (++cntEQ)) || ((b > 0) && (++cntBPlus[a + 1])) || ((b > 0) && (++cntBMinus[a + 1]));

は、

((b == 0) && (++cntEQ)) || ((b > 0) && (++cntBPlus[a + 1])) || ((b < 0) && (++cntBMinus[a + 1]));

if (b == 0) cntEQ++;
else if (b > 0) cntBPlus[a + 1]++;
else if (b > 0) cntBMinus[a + 1]++;

は、

if (b == 0) cntEQ++;
else if (b > 0) cntBPlus[a + 1]++;
else if (b < 0) cntBMinus[a + 1]++;

です。

※最後の b の比較が反対。

投稿日時 - 2009-12-14 13:56:23

ANo.8

No.7 です。

さらに修正。
よく見れば、cntEQは配列である必要はありません。
で、どうせ3回カウントアップするのなら、


int cntBPlus[3] = {0};
int cntBMinus[3] = {0};
int cntEQ = 0;

cntEQ += (b == 0);
cntBPlus[a + 1] += (b > 0);
cntBPMinus[a + 1] += (b < 0);

で、cntEQ + cntBPlus[0] + cntBMinus[2] が答えになるかと。

さらには、

int cntBPlus[3] = {0};
int cntBMinus[3] = {0};
int cntEQ = 0;

((b == 0) && (++cntEQ)) || ((b > 0) && (++cntBPlus[a + 1])) || ((b > 0) && (++cntBMinus[a + 1]));

で、答えは、cntEQ + cntBPlus[0] + cntBMinus[2] ですというのも、ありかもしれません。

ちなみに、上の式は、

if (b == 0) cntEQ++;
else if (b > 0) cntBPlus[a + 1]++;
else if (b > 0) cntBMinus[a + 1]++;

と、概ね、同じ動きをします。(ちょっとちがいます)

投稿日時 - 2009-12-14 13:52:22

ANo.6

> 数百、数千万回とループさせる場合、if文や掛け算
> 処理に時間がかかるからなるべく使うな、

あ、これは、今では迷信に近いです。
特に、「最適化」という技術が発達していますから、

C言語くらいだったら、

・やりたいことをなるべくシンプルに
・あとは、コンパイラにお願い

というのが正しいやり方です。
もしくは、

かけ算や比較の時間を稼ぐ前に、アルゴリズムを見直そう

というのが、正しいやり方である可能性は非常に高いです。

ちなみに、普通のパソコンであれば、

・if による比較は、浮動小数点のかけ算よりは
 速い

・if を文面から追い出すと、(例えば、
cnt += (a < 0) * ( b > 0) + (a > 0) * ( b < 0) + (b == 0);
など……a, b の符号の組み合わせを確認してみよう)
見えないところで、一杯 if が発生する

・浮動小数点は、乗除より、加減のほうが、「遅い」

・人間が小手先で最適化すると、コンパイラの最適化を邪魔する

おまけ。

・3日かかる処理を1日で終わらせようとして、4日かかったら、時間の無駄

・スピードアップは、感覚ではなく「計測」から

というのを意識すると良いでしょう。
ただし、「3日かかる処理を……」というのは、それによって得られる知的な成果を含めれば、一概にそうとはいえません。

投稿日時 - 2009-12-14 13:05:46

補足

数百、数千万回とループするプログラムでもコンパイラが自動的に最適化してくれるのなら、
あまり深く考えすぎる必要はなさそうですね。
単純に、符号が違う数を掛けると負になるのを利用して、
if(a*b<=0)count++ とかでいいのでしょうか。

投稿日時 - 2009-12-14 16:29:42

ANo.5

#1 と同感. なぜ if を使いたくないのか.
まあ, そもそも「変数a(1か-1が入る)と変数b(小数が入る)の符号が違ったらカウントを増やすプログラム」というのがよくわからんのだが.
あと, b が 0 のときにはどうするつもりなのか.

投稿日時 - 2009-12-14 11:33:15

補足

すみません、書き忘れていました。
bが0のときはカウントを増やさないといけません。
ただ、bの符号を取ることができればこの部分は自力でできると思います。

投稿日時 - 2009-12-14 12:39:07

ANo.4

#include <stdio.h>

int main(void)
{
double a;

for(a = -5; a <= 5; ++ a) printf("%f %d\n", a, -1 + (a >= 0) + (a > 0));
return 0;
}

投稿日時 - 2009-12-14 09:49:53

ANo.3

三項演算子を使うのはだめですか?
((b==0)?0:((b<0)?-1:1))

投稿日時 - 2009-12-14 09:36:21

補足

三項演算子ですか。
if文と比べて処理が速くなるのかわかりませんが、これが使えれば一番楽みたいです。
とりあえず調べてみます。

投稿日時 - 2009-12-14 12:36:16

ANo.2

>if文(とできれば掛け算)を使わずに、変数a(1か-1が入る)と変数b(小数が入る)の符号が違ったらカウントを増やすプログラムを作らないといけないのですが、
これだけならばビットシフトとexorを使えばよいかと。
負の数というのは最上位ビットが1である事を利用すると良いと思います。

投稿日時 - 2009-12-14 08:01:34

補足

なるほど、ビットシフトを使えば処理を速くできそうです。
変数aはint型なので15ビット右にシフトすればよさそうですが、
double型の変数bは何ビットシフトすればいいのでしょうか。

投稿日時 - 2009-12-14 12:29:02

ANo.1

そもそも何故 if 文を使いたくないのか補足にどうぞ。

投稿日時 - 2009-12-14 07:33:04

補足

この部分は課題として作成するプログラムの一部なのですが、
数百、数千万回とループさせる場合、if文や掛け算は処理に時間がかかるからなるべく使うな、
この部分も処理を軽くするためにif文を使わないで書け、と言われたのです。

投稿日時 - 2009-12-14 12:05:54

あなたにオススメの質問