掲示板利用宣言

 次のフォームをすべてチェックしてからご利用ください。

 私は

 題名と投稿者名は具体的に書きます。
 課題の丸投げはしません。
 ソースの添付は「HTML変換ツール」で字下げします。
 返信の引用は最小限にします。
 環境(OSとコンパイラ)や症状は具体的に詳しく書きます。
 返信の付いた投稿は削除しません。
 マルチポスト(多重投稿)はしません。

掲示板2

管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    ログ    タグ一覧

No.23790

クイズ:%(剰余演算子)の実装
投稿者---RiSK(2005/10/23 00:49:21)


クイズ

% および標準ライブラリを使わずに剰余を返す
int Remain(int numer, int denom);
を実装してください。

ただし,numer は分子,denom は分母とし,剰余が商より大きくなることはないとします。


挑戦者の回答・質問は自由。
常連による模範解答・質問の回答は2005/10/24以降解禁とします。
ただし,スレッドが落ちそうなときはこの限りではありません。


この投稿にコメントする

削除パスワード

発言に関する情報 題名 投稿番号 投稿者名 投稿日時
<子記事> Re:クイズ:%(剰余演算子)の実装 23794 まきじ 2005/10/23 01:14:59
<子記事> Re:クイズ:%(剰余演算子)の実装 23795 iijima 2005/10/23 02:44:29
<子記事> Re:クイズ:%(剰余演算子)の実装 23796 名前はまだない 2005/10/23 06:13:40
<子記事> Re:クイズ:%(剰余演算子)の実装 23805 まきじ 2005/10/24 00:01:00
<子記事> Re:クイズ:%(剰余演算子)の実装 23806 shu 2005/10/24 00:27:37
<子記事> Re:クイズ:%(剰余演算子)の実装 23807 RiSK 2005/10/24 00:53:59


No.23794

Re:クイズ:%(剰余演算子)の実装
投稿者---まきじ(2005/10/23 01:14:59)


>% および標準ライブラリを使わずに剰余を返す を実装してください。

すごく単純だけどできた・・と思います。
これって、そんなに難しいのかな・・・簡単な算数ですよね?
何か引っ掛けでもあるのかなぁ〜


この投稿にコメントする

削除パスワード

No.23795

Re:クイズ:%(剰余演算子)の実装
投稿者---iijima(2005/10/23 02:44:29)


>剰余が商より大きくなることはないとします。

5 ÷ 3 = 1 あまり 2
剰余が商より大きくなる場合はいくらでもありますが?


この投稿にコメントする

削除パスワード

No.23798

Re:クイズ:%(剰余演算子)の実装
投稿者---RiSK(2005/10/23 13:38:21)


>>剰余が商より大きくなることはないとします。
>
>5 ÷ 3 = 1 あまり 2
>剰余が商より大きくなる場合はいくらでもありますが?

…何書いているんだ>私
大変迷惑をおかけしました。


訂正(上記,私の引用を撤回):

-5 / 3 の結果は -1 余り -2
5 / -3 の結果は -1 余り 2

とします。


この投稿にコメントする

削除パスワード

No.23799

Re:クイズ:%(剰余演算子)の実装
投稿者---まきじ(2005/10/23 14:23:47)


>-5 / 3 の結果は -1 余り -2
>5 / -3 の結果は -1 余り 2

-5 / -3 の結果は 1 余り -2 で OK ですよね?

因みに、Google に計算させてみると

-5 / 3 = -1.66666667
-5 modulo 3 = 1

5 / -3 = -1.66666667
5 modulo -3 = -1

-5 / -3 = 1.66666667
-5 modulo -3 = -2

となりました


この投稿にコメントする

削除パスワード

No.23800

Re:クイズ:%(剰余演算子)の実装
投稿者---とおり(2005/10/23 14:38:13)


C言語の剰余の話は微妙なところですが、
http://seclan.dll.jp/c99d/c99d05.htm#dt19990607
これで良い??


この投稿にコメントする

削除パスワード

No.23796

Re:クイズ:%(剰余演算子)の実装
投稿者---名前はまだない(2005/10/23 06:13:40)


ネタレスっぽい不完全回答をしてみます。
こんなんでも、32bitくらいまでで、繰り返さなければ結構実用的な速度で
動いちゃうからある意味怖い…

int Remain(int numer, int denom) {	 // numer >= 0 && denom > 0 でないとハマる
	while (numer > denom) {
		numer -= denom;
	}
	return numer;
}



この投稿にコメントする

削除パスワード

No.23797

Re:クイズ:%(剰余演算子)の実装
投稿者---si(2005/10/23 12:12:57)


>int Remain(int numer, int denom) {// numer >= 0 && denom > 0 でないとハマる
>  while (numer > denom) {
>    numer -= denom;
>  }
>  return numer;
>}
これに符号を保存するコードを付ければ、効率はともかく、1つの解答になると思われます。
</pre>


この投稿にコメントする

削除パスワード

No.23805

Re:クイズ:%(剰余演算子)の実装
投稿者---まきじ(2005/10/24 00:01:00)


>常連による模範解答・質問の回答は2005/10/24以降解禁とします。
解禁日になったので投稿。(2 パターン)

int Remain(int number, int denom){

    int quot;
    
    if(denom == 0){
        printf("denom must not be zero\n");
        exit(0);
    }
    
    quot = number / denom;
    
    return number - quot * denom;
}

int Remain(int number, int denom){

    int sign = 1;
    
    if(denom == 0){
        printf("denom must not be zero\n");
        exit(0);
    }
    
    if(denom < 0) denom *= -1;
    
    if(number < 0){
        sign = -1;
        number *= sign;
    }
    
    while(number > denom){
        number -= denom;
    }
    
    return number * sign;
}



この投稿にコメントする

削除パスワード

No.23806

Re:クイズ:%(剰余演算子)の実装
投稿者---shu(2005/10/24 00:27:37)


適当に、深く考える事なく……

//
//  %(剰余演算子)の実装
//

//
#include <stdio.h>

//
#define remain( a, b )  (a - a / b * b)

//
int Remain( int numer, int denom );

//
//
int main( void )
{
    int a, b;
    
    //
    scanf( "%d%d", &a, &b );
    
    //
    printf( "%d %% %d = %d\n", a, b, a % b );
    
    printf( "Remain( %d, %d ) = %d\n", a, b, Remain( a, b ) );
    
    printf( "remain( %d, %d ) = %d\n", a, b, remain( a, b ) );
    
    return 0;
}

//
int Remain( int numer, int denom )
{
    return numer - numer / denom * denom;
}



この投稿にコメントする

削除パスワード

No.23807

Re:クイズ:%(剰余演算子)の実装
投稿者---RiSK(2005/10/24 00:53:59)


# すでに回答が出ていますが,用意していた解説などを貼り付け
正数だけを考えるなら…
// (1)
int Remain(int numer, int denom) {
    return numer - numer / denom * denom;
}

0除算による未定義動作を防ぐなら…
// (2)
int Remain(int numer, int denom) {
    if (!demon) exit(1);
    return numer - numer / denom * denom;
}

# 入門者・初心者は (1), (2) と同等の答えを出すことができれば合格


さらに負数について考えるなら…

-5 / 3 は -1 余り -2 と -2 余り  1 の二通りの答えがある。

問題の指示より,分母と分子の片方が負数の時の商は
切り上げであることが分かる。
ex. -5 / 3 ≒ -1.6666 ≒ -1
# つまり,商は常に 0 方向へ切り捨てられる

処理系が,C99 準拠であるか,または C99 準拠ではないが
/ (除算演算子)が商を 0 方向へ切り捨てる実装を
選択しているならば (2) で十分である。
  
そうではない場合,除算演算子を自分で実装して
// (3)
int Divide(int numer, int denom) {
    // 実装
}

int Remain(int numer, int denom) {
    return numer - Divide(numer, denom) * denom;
}

と除算演算子を置き換えるか,

// (4)
int Remain(int numer, int denom) {
    // 全く別のアプローチで実装
}

とする必要がある。
初心者も (3), (4) に挑戦するがよろし。



この投稿にコメントする

削除パスワード

No.23809

Re:クイズ:%(剰余演算子)の実装
投稿者---かずま(2005/10/24 02:02:40)


> # すでに回答が出ていますが,用意していた解説などを貼り付け

割り切れるときに余りが 0 にならない解答もあるようですが。


> // (4)
> int Remain(int numer, int denom) {
>     // 全く別のアプローチで実装
> }

コンピュータの基本を理解するためなら、

#include <limits.h>

int Remain_u(unsigned int x, unsigned int y)
{
    int i = sizeof(int) * CHAR_BIT;
    while (i--)
       if (x >> i >= y) x -= y << i;
    return x;
}

int Remain(int numer, int denom)
{
    /* if (denom == 0) exit(1); */
    if (denom < 0) denom = -denom;
    return (numer < 0) ? -Remain_u(-numer, denom) : Remain_u(numer, denom);
}

携帯や PDA などによく使われている ARMプロセッサには除算命令がないから、
シフトと減算で似たようなことをやっているはずです。



この投稿にコメントする

削除パスワード

No.23950

Re:クイズ:%(剰余演算子)の実装
投稿者---かずま(2005/11/02 21:57:46)


> 割り切れるときに余りが 0 にならない解答もあるようですが。

結局それらの解答は訂正されませんでしたね。例え、CPU が速いとはいえ、
割り算を引き算の繰り返しでやろうというのは無理があります。
小学生だって、365 / 7 の割り算で、引き算を 52回はやらないでしょう。

ということで、シフトと減算と加算で、余りと商を求めるプログラムを示します。
ループを常に 32回まわるのは無駄なので、その回数をちょっと減らす工夫を
してみました。
#include <stdio.h>

void divide(int numer, int denom, int *quot, int *rem)
{
    unsigned int x = (numer < 0) ? -numer : numer;
    unsigned int y = (denom < 0) ? -denom : denom;
    unsigned int z = 0;
    unsigned int i = !(x >>  8) ?  8 :
                     !(x >> 16) ? 16 :
                     !(x >> 24) ? 24 : 32;
    do {
        if (x >> --i >= y) {
            x -= y << i;
            z += 1 << i;
        }
    } while (i);
    if (rem) *rem = (numer < 0) ? -x : x;
    if (quot) *quot = (numer ^ denom) < 0 ? -z : z;
}

int main(void)
{
    int a, b, q, r;

    while (scanf("%d%d", &a, &b) == 2) {
        divide(a, b, &q, &r);
        printf("%d / %d = %d, %d %% %d = %d\n", a, b, q, a, b, r);
    }
    return 0;
}



この投稿にコメントする

削除パスワード

No.23975

[添削] クイズ:%(剰余演算子)の実装
投稿者---RiSK(2005/11/04 23:33:45)


>> 割り切れるときに余りが 0 にならない解答もあるようですが。
>
>結局それらの解答は訂正されませんでしたね。

このままでは良くないので,
今更ですが僭越ながら添削させていただきます。

>No.23796 名前はまだないさん
> ネタレスっぽい不完全回答をしてみます。
>siさん
>これに符号を保存するコードを付ければ、効率はともかく、1つの解答になると思われます。

正数 % 正数 すら正しい答えを返しません。
Remain(100, 100) が 0 ではなく,100 を返します。
せめて
    while (numer > denom) {
    while (numer >= denom) {
にする必要があります。

>No.23805 まきじ さん
2番目のコード
名前はまだないさんと同じ問題があります。

>No.23806 shuさん
マクロ remain に問題があります。
#include <stdio.h>

#define remain( a, b )  (a - a / b * b)

int main(void) {
    int remainder = remain(1-1 /* 0? */, 1);
    printf("0 %% 1 = %d?\n", remainder);
    return 0;
}

>No.23807 RiSK
さらに私まで。(2)のコード。コンパイル通りません。←バカ
    if (!demon) exit(1);
    if (!denom) exit(1);
に訂正しなくてはなりません。


この投稿にコメントする

削除パスワード

No.23978

Re:クイズ:%(剰余演算子)の実装
投稿者---RiSK(2005/11/05 01:04:44)


毎回有用なコードを挙げてくださり感謝します。
感想中心ですが,疑問もあるので教えてください。

>ということで、シフトと減算と加算で、余りと商を求めるプログラムを示します。

ふむふむ。十進数の割り算の筆算と同じ考え方なわけですね。


>ループを常に 32回まわるのは無駄なので、その回数をちょっと減らす工夫を
>してみました。
>    unsigned int i = !(x >>  8) ?  8 :
>                     !(x >> 16) ? 16 :
>                     !(x >> 24) ? 24 : 32;

4bit単位あるいは16bit単位でないのはなぜですか?
分けるbitを細かくすれば,元のループと変わらなくなるのは
分かりますが,どのように最適化するのでしょうか?
考え方を教えてください。(単純な気もするが…)


>    if (quot) *quot = (numer ^ denom) < 0 ? -z : z;

分かったときには目から鱗でした。符号で使われる最上位ビットだけに
注目すればよいわけですね。



この投稿にコメントする

削除パスワード

No.23986

Re:クイズ:%(剰余演算子)の実装
投稿者---かずま(2005/11/06 01:23:40)


>>ループを常に 32回まわるのは無駄なので、その回数をちょっと減らす工夫を
>>してみました。
>>    unsigned int i = !(x >>  8) ?  8 :
>>                     !(x >> 16) ? 16 :
>>                     !(x >> 24) ? 24 : 32;
>
> 4bit単位あるいは16bit単位でないのはなぜですか?
> 分けるbitを細かくすれば,元のループと変わらなくなるのは
> 分かりますが,どのように最適化するのでしょうか?
> 考え方を教えてください。(単純な気もするが…)

単純ですよ。常に 32回まわるのが無駄ということから、
unsigned int i = !(x >> 16) ? 16 : 32; でもよいし、
unsigned int i = !(x >> 12) ? 12 : !(x >> 24) ? 24 : 32; でもよいで
しょう。あまり細かくすると余計に時間がかかって意味がなくなるという
ことで、適当です。

本当は、unsingned int i = LeftMostOne(x) - LeftMostOne(y) + 1;
としたいところです。ここで、LeftMostOne(x) は
x の最上位の 1 のビット位置を求めるものとします。

#define LeftMostOne(x) ((int)(log(x) / log(2))

と定義しても、誤差のため、正しい値にならないこともありますし、
log を使って速くなるわけがありませんから、本末転倒です。
ハードウェアにそういう命令があればよいのですが、そうなるとアセンブラで
書くしかなくなるでしょう。DEC の VAX や Motorolla の MC68020 にはそんな
命令があったような気がするんですが、Intel の Pentium はどうなんでしょう。



この投稿にコメントする

削除パスワード

No.23997

Re:クイズ:%(剰余演算子)の実装
投稿者---RiSK(2005/11/08 01:41:54)


> あまり細かくすると余計に時間がかかって意味がなくなるという
ことで、適当です。

分かりました。ありがとうございます。


# 以下,処理系依存&脱線気味です。

>ここで、LeftMostOne(x) は
>x の最上位の 1 のビット位置を求めるものとします。
>(snip)
>Intel の Pentium はどうなんでしょう。

IA-32 インテル - アーキテクチャソフトウェア・デベロッパーズ・マニュアル 中巻A:命令セット・リファレンスA-M P95より BSR - Bit Scan Reverse 説明 ソース・オペランド(第2 オペランド)を検索して、 最上位のセットビット(値1 のビット)を探す。 最上位のセットビットが見つかると、そのビット・インデックスが デスティネーション・オペランド(第1 オペランド)にストアされる。
Windows XP Pro/VC6 with インラインアセンブラ/AMD Duron (Applebred) 1600.00 MHz: void divide(int numer, int denom, int *quot, int *rem) { unsigned int x = (numer < 0) ? -numer : numer; unsigned int y = (denom < 0) ? -denom : denom; unsigned int z = 0; int i; __asm { push eax ; eax 保存 push ebx ; ebx 保存 bsr eax, x ; eax は x の最上位ビットのインデックス jnz X_END ; ただし ZF がセットされたときは xor eax,eax ; eax が未定義なので 0 を代入 X_END: bsr ebx, y ; ebx は y の最上位ビットのインデックス jnz Y_END ; ただし ZF がセットされたときは xor ebx, ebx; ebx が未定義なので 0 を代入 Y_END: sub eax, ebx; eax -= ebx; inc eax ; ++eax; mov i, eax ; i = eax; pop ebx ; ebx 戻す pop eax ; eax 戻す } while (i-- > 0) { if (x >> i >= y) { x -= y << i; z += 1 << i; } } if (rem) *rem = (numer < 0) ? -x : x; if (quot) *quot = (numer ^ denom) < 0 ? -z : z; } ASM書くのは初めてだったり。 読む事はあるのだけどね。 # コードが間違ってたので修正版UP # i が unsigned だったのではまりました…。



この投稿にコメントする

削除パスワード

No.24123

Re:クイズ:%(剰余演算子)の実装
投稿者---かずま(2005/11/13 01:22:40)


> BSR - Bit Scan Reverse

やはり、Pentium にもあるんですね。
次のように書いて -O2 オプションでコンパイルすると結構いいコードが
出るようです。VC++ Toolkit 2003 のコンパイラです。
コードを見るのには、-FAc で .cod ファイルを生成します。
#include <stdio.h>

__inline int bsr(unsigned int x) { __asm bsr eax, x }

void divide(int numer, int denom, int *quot, int *rem)
{
    unsigned int x = (numer < 0) ? -numer : numer;
    unsigned int y = (denom < 0) ? -denom : denom;
    unsigned int z = 0;
    if (x && y) { 
        int i;
        for (i = bsr(x) - bsr(y); i >= 0; i--)
            if (x >> i >= y) {
                x -= y << i;
                z += 1 << i;
            }
    }
    if (rem) *rem = (numer < 0) ? -x : x;
    if (quot) *quot = (numer ^ denom) < 0 ? -z : z;
}

int main(void)
{
    int a, b, q, r;

    while (scanf("%d%d", &a, &b) == 2) {
        divide(a, b, &q, &r);
        printf(" %d / %d = %d, %d %% %d = %d\n", a, b, q, a, b, r);
    }
    return 0;
}



この投稿にコメントする

削除パスワード

No.23812

Re:クイズ:%(剰余演算子)の実装
投稿者---まきじ(2005/10/24 12:03:33)


「絶対値で計算してから、どちらかのオペランドがマイナスならマイナスを付加する。」
と考えた。除算演算子使ったら駄目なのかな・・

int Divide(int numer, int denom){

    int sign = 1;
    
    if(numer < 0){ sign = -1; numer *= -1; }
    if(denom < 0){ sign *= sign; denom *= -1; }
    
    return numer / denom * sign;

}



この投稿にコメントする

削除パスワード

管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    ログ    タグ一覧