裏表(Phinloda のもう裏だか表だか分からないページ)

コンピュータ・プログラミング系の話がメインのそれなりにごちゃごちゃしたネタばかり出てくるサイトです。多分。
直線と点との距離を求める

知恵袋に、直線と点との距離を求めるにはどうするかという質問があったので、C言語で書いてみたら、最終的に、こんな感じになった。

#include <math.h>

double distance(double lineX1, double lineY1, double lineX2, double lineY2,
    double dotX, double dotY) {
    double a, b, c; /* ax + by + c = 0 */
    double root;
    double dist; /* 求める距離 */

    a = lineY1 - lineY2;
    b = lineX2 - lineX1;
    c = (-b * lineY1) + (-a * lineX1);
    root = sqrt(a*a + b*b);

    if (root == 0.0) {
        return -1.0; /* 求められない場合は負の値を返す */
    }

    dist = ((a * dotX) + (b * dotY) + c) / root;

    if (dist < 0.0) {
        dist = -dist;
    }

    return dist;
}
公式などは調べたら分ると思うので説明は省く。単に公式に当てはめているようなものである。 ところで、これは実は投稿後に修正したのである。 厳密にいえば細かいところは違っているが、 修正前のロジックの一部は次のような感じだった。
    a = lineY1 - lineY2;
    b = lineX2 - lineX1;

    if (a == 0.0 && b == 0.0) {
        return -1.0; /* 求められない場合は負の値を返す */
    }

    c = (-b * lineY1) + (-a * lineX1);
    root = sqrt(a*a + b*b);

    dist = ((a * dotX) + (b * dotY) + c) / root;

修正した理由は、a == 0.0 && b == 0.0 が成立しなくても、sqrt(a*a + b*b) が 0.0 になることがあるだろうと考えたからである。 a も b も 0.0 に極めて近い場合に a*a も b*b も 0.0 になってしまう可能性があるから、おそらくこの判断は正しい。

修正前のロジックは、sqrt の処理は比較的重いだろうと考えて、呼び出す前にエラーになる場合を判定しようと考えたのだ。 だから、この判定は c を計算する前に行っている。エラーになるのなら、判定前に c を計算するのは無駄だ。

ところで、sqrt(x) において、x > 0 であれば、sqrt(x) > 0 であることは保証されるか? もしその保証があれば、sqrt を呼び出す前に、a*a + b*b の値が 0.0 かどうかを比較した時点でエラーチェックすることができる。

確かめてみると、ISO の draft (2007/9/7版) を見ても、引数が less than zero だと domain error になると書いてあるが、それだけである。 数学的には、0 < x < 1 の場合に、x < √x が成り立つのだから、x > 0 であれば sqrt(x) > 0 も成り立って欲しいのだが、0に極めて近い値が与えられたときに、実装の制限によって0が返される可能性があるかどうか、そこがどうも分らない。

ということで、確実なのは sqrt を求めた後に 0.0 と比較することだろうという結論に至ったのである。

JUGEMテーマ:コンピュータ
| C言語 | 11:07 | comments(0) | trackbacks(0)
Yahoo!知恵袋に出ていたC言語の質問への凄い回答 (3)

さらに昨日の投稿の続きである。 元となっている質問、回答は「C言語の問題です。 問1から100までの整数の中に3の倍数がいくつあるかを求めるプロ... - Yahoo!知恵袋」をご覧ください。

次の回答者はedomin2004さん。 私の見た感じでは、この人の回答をパッと見た瞬間に、もしかしてプロの方かなと思った。 文句を付けるようなところが殆どなくて、 他の人の回答に対して指摘したような箇所は全部理想的に書いてある。 そして、一番のポイントは、ここ。

#include <stdio.h>

int main(void){

どこ? #include と int main の間にある空白行だ。

というのは、これが無意識に書けないと、まずプロではない。 もしかすると、プロでなくても、プログラムを数年書き続けていれば、 こういう所が意識してなくても勝手に書けるようになるのかもしれない。

もちろん空白行があろうがなかろうが、C言語のプログラムの動作には影響しない。 全く同じ動作になるはずだ。 しかし、プログラムは動作すればいいというものではない。 そこを学校ではよく教えて欲しいと思う。

もっとも、動作しないという論外の状態では、それどころではない。 もちろん、このプログラムは正しい結果を表示する。 ていうか、正しい結果が出るのはこれと次の回答しかないのだが。

最後の回答は凄い。特にメインの処理のあたりが。

int main(void) {
    printf("%d¥n", 100 / 3);

    return 0;
}

ちなみに、結果は正しい。 ただ、問題には「I%3を利用」という条件がある。 ここが満たされてないので、解答としては減点されてしまうかもしれない。 しかし、この回答者さんは故意に書いたんだろうな、多分。

しかし私がびっくりしたのはそこではないのだ。 この人、どうやってインデントしたままの回答を投稿したのだろう?? Yahoo! 知恵袋は連続した空白を勝手に削除するという恐ろしい仕様があるはずなのである。 謎だ。

JUGEMテーマ:コンピュータ
| C言語 | 23:06 | comments(0) | trackbacks(0)
Yahoo!知恵袋に出ていたC言語の質問への凄い回答 (2)

昨日の投稿の続きである。 元となっている質問、回答は「C言語の問題です。 問1から100までの整数の中に3の倍数がいくつあるかを求めるプロ... - Yahoo!知恵袋」をご覧ください。

ベストアンサー以外の回答を見ていこう。 まず、monomorai34さんの回答。

int j;//3の倍数の数

初期化するというのは、そんなに難しいことなのだろうか。

ていうか、学校で教えないのだろうか。あるいは独学かもしれないが。まあいいけど、このプログラムも実行すると

機100の整数の中に3の倍数は2674309個あります。

と表示されるのだが、もしかして最近使われている処理系の中には、この結果が正しく出るようなものがあるのかもしれない。 Java だったら問題ないんだけど。

なお、for ループの中で変数 i が宣言されているが、この書き方は C99 から追加された仕様なので、古いコンパイラだとコンパイルできないかもしれない。gcc を使う場合は -std=c99 というオプションを使えば対応できる。 古いっていっても、C99 ができてから10年以上経っているんですけどね。

コメントについて。

if(i%3==0) j++;//変数iがさ3の倍数だったら変数jに1を追加

「さ3の倍数」というのは typo か何かだと思うので気にしないが、原則として、このコメントは必要ない。こんな内容はC言語でプログラムを書ける人なら誰でも分かる。

ただ、この回答は質問をしている初心者の方へ説明のためのものだから、あえて必要ないコメントを書いて説明したかったのだと思う。そう理解すれば親切でよいと思うが、初心者の皆さんは、実際のコードにこんなコメントを書かないように気をつけて欲しい。

次にカテゴリマスタのe165850_kiyosekiさんの回答。 要するに自分で書けというのだが、 これは基本的に素晴らしい。だから最後の著作権保護法の所が蛇足なのが惜しい。 回答としてはこれがベストアンサーでもいい位だ。

もう一つだけ違和感が残るのは「アルゴリズムを生み出す力」というところである。 基本的に、アルゴリズムは生み出すものではなく、他の人が作ったパターン当てはめるという感覚になると思う。 いろんなプログラムを読んで、こんなやり方があるのか、と驚く。それがまず基本だ。 素人ならともかく、もしプロなら自分でアルゴリズムを生み出していくようではいけない。

それにしても質問にコードが出ていないのは残念だ。 コードが書いてあれば、このように評も出来るのだから、学習の助けにもなると思うのだが。

(つづく)

JUGEMテーマ:コンピュータ
| C言語 | 22:10 | comments(0) | trackbacks(0)
Yahoo!知恵袋に出ていたC言語の質問への凄い回答 (1)

最近のプログラマーの質が落ちているという噂は大昔から耳にしているが、案外そうでもないのでは、と思っている。根拠も何もありません。もちろんヒドいのはいるとしても、そういうのは昔からいたし、程度の話。

Yahoo!知恵袋に出ていたC言語の質問の回答なのだが、ここまで凄いのかというのがあったので面白いから紹介したい。 その前に、カテゴリマスターの回答者さんが次のように書いている。

ここでコードを書いて貰って、許可を得ずにそれをそのまま使用すると著作権保護法に抵触します

現実的には、知恵袋で回答したごときのコードに著作権が発生するかどうか微妙だと思うが、少なくとも日本国には著作権保護法などという法律は存在しないので、もちろん抵触しない。

日本にあるのは著作権法という法律で、その第三十二条に、

公表された著作物は、引用して利用することができる。この場合において、その引用は、公正な慣行に合致するものであり、かつ、報道、批評、研究その他の引用の目的上正当な範囲内で行なわれるものでなければならない。

と定められているので、勝手に引用させていただく。 もちろん知恵袋に投稿された内容は「公表された」ものであり、今からそれを批評しようというのだから条件はクリアしているだろう。

なお、出所は「C言語の問題です。 問1から100までの整数の中に3の倍数がいくつあるかを求めるプロ... - Yahoo!知恵袋」である。(http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1293736492)

余談だが、今書いている内容は、以前紹介した Windows7 が勝手に再起動したトラブルで消滅した書きかけのドキュメントの一部で、思い出しながら書いているのだが、出所が分からなくなって難儀した。検索して探すにも限界がある。結局見つけたのだが。

問題は、C言語で、1から100までの整数の中に3の倍数がいくつあるかを求めるプログラムを作れというものだ。 ただし、条件として、

3の倍数の判定にはI%3を利用

と書いてある。もしこれが I という名前の変数を使えという所まで条件だと考えたら、回答者は全滅だが、まあそこまで文字通りに解釈する必要もないだろう。

まずベストアンサーがいきなり間違っているのが知恵袋っぽくてよいと思った。 そこから評させていただく。 回答は hidenari_1 さん。 ループの中で3で割り切れるかどうかの判定部分がこうなっている。

if(i % c == 0)b = b + 1;

c って何?

条件の「I%3を利用」というのが読めなかったのだろうか? ちなみに c という変数は定義されていないから結果がどうこう以前に、このコードはコンパイルすら通らない。

これでは先に進めないからとりあえず、c を 3に変更してコンパイルして実行してみたら、次のような結果が表示された。

三の倍数は、2674309個です。

結構たくさんありますね

じゃなくて、カウンタが初期化されてないだろ。中学生の初心者プログラマーみたいな間違いをするなよと突っ込みたくなるが、実際はプロとか、具体的に言うのは避けるが世界一のシェアを持っている某パソコン用OSの開発者でもやるバグだから、仕方ないのかもしれない。

落ちついて、ベストアンサーを最初から見ていこう。

int a, b, i;

まずここがダメ。 間違いではない。C言語としては正しいしプログラムとしてもok。でもダメだ。a とか b というような変数名は使ってはいけない。意味が分からないからだ。

ちなみに、i という変数は使ってもいい。i は、暗黙の了解事項として、ループ変数として使うという慣習があるからである。ここは、例えば次のように書くべきである。

int count = 0;
int i;

b という変数を count という名前にしてみた。これで何かを数えているということが分かる。 変数 a ですか? 使っていないので削除しましたが、なにか?

もう一回戻って、ループのところ。

for(i = 1; i < 101; i ++)
{
if(i % c == 0)b = b + 1;
}

インデントできないのは勝手に空白を削除してしまう知恵袋の仕様があるので仕方ない。 とりあえず、インデントだけ勝手に付けるとこうなる。

for(i = 1; i < 101; i ++)
{
    if(i % c == 0)b = b + 1;
}

空白論争は不毛なことが多いが、一応個人的な好みのスタイルで書いておく。

for (i = 1; i < 101; i++) {
    if (i % c == 0)
        b = b + 1;
}

ここで注目したいのはあと2つ。 まず、i < 101 ではなく、i <= 100 とすべきである。 なぜなら、問題には「100までの整数」と書いてあるからだ。 「100までの整数」に合う条件はストレートに考えたら i <= 100 である。 「i < 101」は、「101未満の整数」である。両者は結果は同じだが、意味は違う。

そして、b = b + 1 ではなく、b++ と書くべきである。 なぜなら、++ には「1増やす」という強い意思があって、それはコードを見る人にも必ず伝わるからだ。「b = b + 1」だと、b に 1を加えるという意味になる。もしかして1ではなく2ではないか、と疑う人がいるかもしれない。 そういう曖昧さが残る。

これがなぜベストアンサーに選ばれたのか謎。 しかも回答者はお礼のコメントを書いているのだ。 一体何がどう理解できたのか知りたいものだ。

(つづく)

JUGEMテーマ:コンピュータ
| C言語 | 19:06 | comments(0) | trackbacks(0)
不定とは

3日の投稿で「不定」と書いたのだが、不定という言葉は未定義だというような話が出たので、一応調べてみたのだが、不定というのはK&Rの本に出ていた用語で、本義通りの「定められていない」という以上の意味はないようである。 規格上は「未規定」という言葉があって、これは次のように定義されている。

「この規格が、二つ以上の可能性を提供し、個々の場合にどの可能性を選択するかに関して何ら要求を課さない動作。

(JIS X3010 3.4.4)

さらに、評価順序が規定されている一部の演算子以外においては、

部分式の評価順序及び副作用が生じる順序は未規定とする

(JIS X3010 6.5)

と定められているので、c = a + a++; の結果は規格に従えば未規定になるのではないか、という話なのだ。

JUGEMテーマ:コンピュータ
| C言語 | 22:28 | comments(0) | trackbacks(0)
C言語の模範的なソースって何だろう

こういうことを私が書くと老いて駄馬になったかと言われそうだが、 まあある意味その通りだが別に構わない。 昔なら gcc 読めみたいな話だったが、 今時だと、C言語で書いた模範的な、かつ大規模なソースコードって、 普通は何が推奨されているのだろうか?

GNU 系のプログラムと、Linux のカーネル、というのはまあとりあえず思いつくとして、 Ruby って C で書いてあるのだっけ? そういえば Ruby のソース見たことがないぞ。

 
JUGEMテーマ:コンピュータ
| C言語 | 19:29 | comments(1) | trackbacks(0)
■えるC言語: 共通の処理を switch の外に書く

ふ「ということで、連載では、goto 使っちまえということで、

    switch (type) {
    case FORUM:
        fprintf(fp, "forum");
        goto common;

    case PATIO:
        fprintf(fp, "patio");
        goto common;

    case NETNEWS:
        fprintf(fp, "netnews");

    common:
        /* 共通の処理 */
        break;

    case 他の場合:
        〜省略〜

    }

こんな書き方をしました。」

U「common: というのは?」

ふ「あーこれは、単なるラベルです。goto で飛んでくるときの目印ですね、気にしないでください。簡単にいいますと、goto common; と書くと、common: という所にジャンプできるというわけです。」

U「何がなんだか分かりませんが、ダイクストラが「死んだ」と言ったというアレですね。ところで、連載時の内容には「共通処理を他関数で処理するように書き直した」と書いてありますね。他関数というのは、何をどこに出したのですか?」

ふ「common: ラベルの後のところを外に出すのですよ。

    switch (type) {
    case FORUM:
        fprintf(fp, "forum");
        common(fp);
        break;

    case PATIO:
        fprintf(fp, "patio");
        common(fp);
        break;

    case NETNEWS:
        fprintf(fp, "netnews");
        common(fp);
        break;

    case 他の場合:
        〜省略〜

    }

ふ「もちろん、common(fp) という処理が、この3つの場合以外にも呼ばれるのであれば、switch の外に出すという手もあります。微妙です。common という名前も微妙で、本来、forum_patio_netnews_common みたいな名前であるべきですが、長すぎます。」

U「あっそう。」

ふ「まあ長さはともかく、共通処理というのがどこまで共通なのか、というあたりでややこしいですね。根本的な設計を見直す、というのが正解である可能性もあります。」

U「ところで、なぜ if で書き換えられるのに、swith が必要なのか、という質問にまだ答えてもらっていませんが。」」

ふ「それはCMの後。」

(つづく)

| C言語 | 03:09 | comments(1) | -
■えるC言語: 違った case に共通の処理

ふ「原稿がどこにあるのか分からなくなってしまいアヒ。」

U「10日も放置するからだよ。」

ふ「もともと、FPL というディレクトリの下にあったのですが、 ドタバタと引っ越したときにどこに行ったか分からなくなったわけですが。」

U「つまり、掲載先でファイルを分類してあったのですね、ジャンルとかカテゴリ別ではなくて。」

ふ「連載先が移転するというのは今まで想定していなかったというか、 ここ15年、そういうことはなかったもので。」

U「原稿というディレクトリ掘っておいて、そこに全部入れるといいんじゃないですか?」

ふ「まあそうですね、このようなことがあるのなら。」

U「しかし、原稿がどこにあるか分からないとか書いたら、大量に書きかけのものがありそうな錯覚を感じますが、実際殆どないのでは。」

ふ「まあそういえばそうだ。」

ふ「というわけで、先に前回のコメントからフォローしておきますと、

「フィンローダのあっぱれご意見番「第41回:暗黒面の技」」が参考になりました/なると思いますが、入門向きではないかも。

 ということなのですが、その第41回というのがどこにあるかを探すのも一苦労?」

U「実は google で探せるです。「フィンローダ」「あっぱれ」「暗黒面」という冗談のようなキーワード指定でしっかりトップにヒットします。 http://www.st.rim.or.jp/~phinloda/phin/phin9509.html こんな所に。」

ふ「google 偉い。で、ヤフーとかどうなのですか、ちなみに?」

U「Yahoo Japan は惜しいというか、インデックスページを先にヒットさせるので、どんぴしゃのページは2位でした。まあ誤差の範囲内でしょう。AlatVista もヒットします。

ふ「Infoseekで「infoseekの検索結果」だと、Cマガジンのサイトが先に出てきますね、どちらがどうという話ではないのですが、アルゴリズムがどうなっているのか、というのには興味があります。」

U「そろそろ本題に入りますけど、そういう場合は一体どうすればいいのかなと。」

ふ「case のそれぞれに対して共通の処理がある、というような場合ですか、迷わず別関数にしてしまうというのはどうだろう?、という話が出ていた。」

U「先に問題を書いておきますと、type の値に関わらず殆ど共通の処理があって、もし全部共通なら、

    switch (type) {
    case FORUM:
    case PATIO:
    case NETNEWS:
        /* 共通の処理 */
        break;

    case 他の場合:
        〜省略〜
    }

 このように書けるのですが、ちょっとだけ共通でない処理があるために、

    switch (type) {
    case FORUM:
    case PATIO:
    case NETNEWS:
        if (type == FORUM) {
            fprintf(fp, "forum");
        } else if (type == PATIO) {
            fprintf(fp, "patio");
        } else {
            fprintf(fp, "netnews");
        }
        /* 共通の処理 */
        break;

    case 他の場合:
        〜省略〜

    }

 こんな書き方するのはいかがなものか、という話です。」

U「連載には「他の場合」というところがありませんでしたが?」

ふ「今こっそり追加しました。」

U「 _| ̄|○

(つづく)

| C言語 | 01:17 | comments(0) | -
■えるC言語: case と default

ふ「switch 文の動作はちと面白いように思います。例えば、こういう書き方をしても、C言語としては別に問題ありません。

    switch (i) {
    case 0:
        foo();

    case 1:
        bar();

    default:
        printf("do nothing¥n");
    }

U「これは?」

ふ「このように書くと、i の値が 0 なら、foo(); を実行する、というところまでは先ほどの例と同じですが、続けて bar(); も実行して、さらに do nothing と画面に表示していきます。」

U「i の値は、どこから処理を始めるかということを決めるだけであって、break がなければ、そこから switch 文の最後まで全部実行するのですね。」

ふ「そういうことです。if を使って書いた、次のような基本的な処理の流れの場合、

    if (i == 0) {
        foo();
    } else {
        bar();
    }

 これを switch を使って書くと、次のようになります。

    switch (i) {
    case 0:
        foo();
        break;

    default:
        bar();
        break;
    }

U「ところで、default というのは何と読むのですか?」

ふ「デフォルトと読みます。switch 文の場合、どの case にも該当しない場合に、そこから処理を始めるという意味を持ちます。」

U「case というのは?」

ふ「ケースです。普通に英語で「場合」という意味ですが、日本語でもケースといいますね。ちなみに、switch の直後の () の中で指定された変数が case に続く値と一致していたら、そこから処理を始めることになります。」

U「さきほどの例を比べると、if を使った方が圧倒的に分かりやすいような気がしますが、なぜ switch 文が必要なのでしょうか?」

(つづく)

| C言語 | 02:44 | comments(2) | -
■えるC言語: switch 文

ふ「萌えろいい女、という歌がありましたね。」

U「ないよ」

ふ「まあそれはおいといて、break の話ですけど。break文は、それを囲む最も内側の switch 文または繰り返し文の実行を終了させる、という決まりになっています。」

U「switch 文というのは?」

ふ「こんなのですね。

    switch (i) {
    case 0:
        foo();
        break;

    case 1:
        bar();
        break;

    default:
        printf("do nothing¥n");
        break;
    }

 変数 i がセットされる処理は省略しているのですが、この例だと、i の値が 0 のときには、foo(); が実行されます。1 なら、bar(); が実行されます。どちらでもない場合には、do nothing と画面に表示することになります。」

U「しかし、どこからどこまでが文なのか、分からないのですが。」

ふ「とりあえず、switch というところから、最後の } が出てくるまで、全部です。」

U「全部ひっくるめて文なのですね。なるほど。」

(つづく)

| C言語 | 01:53 | comments(0) | -
 1/2PAGES >>
Powered by "JUGEM"
▲このページの先頭へ
CALENDAR
S M T W T F S
      1
2345678
9101112131415
16171819202122
23242526272829
30      
<< September 2018 >>
NEW ENTRIES
CATEGORIES
ARCHIVES
NEW COMMENTS
NEW TRACKBACKS
LINKS
PROFILE