東京工芸大学 工学部 電子機械学科 2年 前期 

基礎プログラミング 第3回
 2017.4.25
藤木 文彦

http://fujiki.tv/t-kougei/kisoprog/
fujiki.kougei@gmail.com



【授業の進め方

 解説を読み、各プロジェクトを実行したのち、ページ後半にある「課題」にある指示に従ってプログラムを一部改変、実行して、指示に従って、プログラムと実行結果、考察を行ってメールを提出しなさい。

メールは、課題一つを1つのメールとして提出

メールの先頭に

学籍番号 e0000000
氏名    工芸 電子


のように、自分の番号と名前を入れること。(これが無いと提出課題の整理時に見落とされることがある)

なお、メールは、一応出来た時点で提出して構いません。その都度送って下さい。
あとでまとめて送る、などすると、「不正コピーをしたのでは無いか」と疑られることになります。

もし修正等がある場合は、再提出して構いません。後から送られたメールの方を採点対象とします。

※ メール送出の際の名前が、大学で指定した 「カタカナ名」 以外のペンネームなどになっている場合、整理の時に未提出扱いとなる事があるので、注意して下さい。
※ 後日、やり残し課題を大学以外の場所から提出するときには、名前がきちんと分かるように提出されていることを確認して下さい。


  【演習 0 】 

先週までの課題のやり残しのある人は、まず、それを始めに行いなさい。

変数ヘンスウカタ算術サンジュツプログラム(2)



 Visual C++ を立ち上げ、プログラムを入力する準備をしておきます。

 いくつかの演習を行うごとにメールを提出してもらいますが、直接メールに書くのではなく、いったんエディタに記録したほうが良いので、TeraPadなど(自分の使いやすいエディタでよい)を立ち上げておきます。
 実行画面のコピー方法は、第1回の最初の部分を参考にして下さい。

変数型と正負の数 signed unsigned

変数にも、正負の数を両方扱う型、(signed)と、正の数だけを扱う型 (unsigned)があります。特別に宣言しないと、正負の数両方を扱う型(符号有り signed )と成りますが、正の数だけを扱う時は、 符号無し(unsigned) 宣言を用います。この宣言は、型宣言の時に、その前に付けます。

 signed short int  -32768 〜 32767
 unsigned short int   0 〜 65535


(なぜ、プラスマイナスで微妙に数が違うかというと、”0”を正の数として扱うからである。詳しくは、数直線を描いて示す。)

扱える数の範囲が異なるので、以下のプログラムの2つめの出力では、 同じ演算の結果であるにもかかわらず、sa と ua の結果が異なります。

  (例題1) 

Project0301


int _tmain(int argc, _TCHAR* argv[])
{
    signed short int sa, sb;
    unsigned short int ua, ub;

    sa = 30000;
    ua = 30000;
        printf("sa= %d, ua= %d\n",sa );
    sa = sa + 30000;
    ua = ua + 30000;
        printf("sa= %d, ua= %d\n",sa );


    getchar();
    return 0;

}


  【演習問題1】 
  Project0301 のプログラムの一部を書き換えます。
 型を short から long に変更し、初期値、加える値を、2000000000 (20億) に変更して実行してみなさい。
 ただし、このとき、出力書式を、 %d(整数型) ではなく、 %ld(倍精度符号付き整数) %lu(倍精度符号無し整数)のように変更しなさい。具体的には、次を参照しなさい。

printf("sa= %ld, ua= %lu\n",sa, ua );

プログラムの主要部分と、実行結果の画面をメール本文にコピーしなさい。




  定数宣言 



上記の様に、何度も同じ数値を使う様な場合、プログラム中に直接数値を書くのではなく、数値の代わりに、文字列を定義して、それを使うようにすると、1カ所変更するだけで、全ての場所を変更したことになります。これは、数値を変えて使用するときに、変更箇所が複数有っても間違えないようにするために有効です。

  (例題2) 

Project0302


#include "stdafx.h"

#define NUM 30000

// 定数宣言の後には ”;” を入れない。
        //main ( _tmain) の前に書いておく

int _tmain(int argc, _TCHAR* argv[])
{
    signed short int sa;
    unsigned short int ua;

    sa = NUM;
    ua = NUM;
        printf("sa= %d, ua= %d\n",sa, ua );
    sa = sa + NUM;
    ua = ua + NUM;
        printf("sa= %d, ua= %d\n",sa, ua );


   getchar();
    return 0;

}


NUM を別の数に変更すれば、何カ所も変更しなくても、いろいろな数について試してみることが出来ます。
 (変数で入力すれば良い場合もありますが、ここでは、文字の置き換えの実例としてこのような方法を用いました。)

定数の使用は、また、次のように、同じものを何カ所でも使うときに有効です。


  【演習問題2】 

 
Project0302 で、
NUM を 2000000000 に変更し、(0は9個)コンパイル実行してみなさい。

プログラムの主要部分と、実行結果の画面をメール本文にコピーしなさい。
もう一桁増やして実行するとどうなるか、実行して結果がどうなったか、考察を書きなさい。


  (例題3) 

円の面積と体積を求めるプログラム。

Project0303


#define PI 3.14159265
#define ROUND 10

int _tmain(int argc, _TCHAR* argv[])
{
    float r, menseki, taiseki;

    r = ROUND;
    menseki = PI * r * r;
    taiseki = ( 4 / 3 ) * PI * r * r * r;
        printf("hankei= %f, menseki= %f, taiseki= %f\n",r , menseki , taiseki );

    getchar();
    return 0;
}

上記プログラムは、そのままでは、一部不具合がある。

どこが不具合の原因か考え、正しく動くようにするように書き換えて、実行してみなさい。


(ヒント:体積は、4188.787 程度になるはずである。 整数数字同士の演算は、結果が整数となるので、 4/3 の結果は、整数になる。(本当は、 4/3= 1.3333.. になって欲しいのだが、1になってしまう。) どちらか少なくとも一方を実数にすれば、結果が実数になるので、 4−>4.0 のように変更するとよい。

 なお、整数型変数どうしでも同じことが起こるので、整数の割り算を実数にしたいときには、

    (float) a / (float) b  

のように、することで、型を強制的に実数型に変換する。この (float) を「キャスト」という。

  【演習問題3】 
 Project0303 を入力、実行してみなさい。このプログラムには、一部プログラムの論理的なミスがあります。
そのため、体積を求めた結果が、正しく求まっていません。
どこを直したらよいのか、本文のヒントを参考に書き直しなさい。(前回説明した、「キャスト」という言葉も参考にすると良い。)

次に、プログラムの一部を変えて、半径、27.3 の円の面積と体積を表示しなさい。
プログラムの主要部分と、実行結果の画面をメール本文にコピーしなさい。


  printf文での書式指定一覧(桁数の指定) 



前回は、printf文での書式指定は、代表的なものを数例掲げるに留めたが、使用できるものの一覧を掲げる。
倍精度の場合は、 long の頭文字 ”l”(エル)を付ける。
 → 実は、普通制度も倍精度も内部の処理が同じなので、出力書式に、以下のように、桁数を指定します。

出力幅を指定することも出来る。出力幅は、整数で

”% 整数部の桁数.小数部の桁数 書式”

のように、指定する。

 例)
  %8.3f
出力は、
  12345.678

のようになります。(小数以下第4ケタ目が四捨五入されます。)


例は、下記の表を参照のこと。
マイナスを指定すると、桁が、左揃えになる。

指定子 対応する型 説明 使用例
%c char 1文字を出力する "%c"
%s char * 文字列を出力する "%8s", "%-10s"
%d int, short 整数を10進で出力する "%-2d","%03d"
%u unsigned int, unsigned short 符号なし整数を10進で出力する "%2u","%02u"
%o int, short,
unsigned int, unsigned short
整数を8進で出力する "%06o","%03o"
%x int, short,
unsigned int, unsigned short
整数を16進で出力する "%04x"
%f float 実数を出力する "%5.2f"
%e float 実数を指数表示で出力する "%5.3e"
%g float 実数を最適な形式で出力する "%g"
%ld long 倍精度整数を10進で出力する "%-10ld"
%lu unsigned long 符号なし倍精度整数を10進で出力する "%10lu"
%lo long, unsigned long 倍精度整数を8進で出力する "%12lo"
%lx long, unsigned long 倍精度整数を16進で出力する "%08lx"
(%lf)
%aa.bbf
double 倍精度実数を出力する
書式の aa は、出力全桁数
bb は、小数点以下の桁数
"%20.12f"


  変数型と誤差の蓄積 



浮動小数点型変数は、扱える桁数(精度)が有限であるために、あまりに小さい数を扱うと、誤差がでる。

たとえば、
1.23456789 を 100000(10万。以下特に注釈のない限り、同じ) で割って、 100000 倍すれば、元の数に戻るはずであるが、実際には、そうならないことがある。
 (ただし、処理系によって、内部での数値の持ち方の有効桁数が異なるために、元に戻る場合もある。どのようになって居るかは、実際に試してみなければならない。)

 以下のプログラムは、
 100000 で割ったあと、100000倍した場合と、
 100000倍してから、割ったものを、足し算で100000回加えた場合である。

 どちらも、正しくもとには戻っていない。
 しかも、表示される結果が異なることに注意せよ。

 上で説明していることを式で書くとこんな感じ(実際は少し違う結果になることがある。)
  100001.23456789/100000 = 1.00001234
    1.00001234*100000 = 100001.23400000 (元に戻らない)


(参考) 浮動小数点形式では、 1.23456789 を 100000 で割ると、
 0.00001234 となるが、実際には、内部では、
 1.23456789 * 10^-5  という形式で記録されるので、有効桁数は変わらない。

  (例題4) 

Project0304


#define NUM 100001.23456789
#define DIV 100000

int _tmain(int argc, _TCHAR* argv[])
{
    float a, i, n, d;
  float total;

    n = NUM;
    d = DIV;

    a = n / d;
    total = a * d;
        printf("n= %20.14f, d= %f, a= %20.14f, b=%20.14f\n" ,n , d , a, total);

    total=0.0;
    for ( i = 1; i <= DIV; i++)
    {
        total += a;
    }
        printf("n= %20.14lf, d= %f, a= %20.14f, b=%20.14f\n" ,n , d , a, total);

    getchar();
    return 0;
}

※注意:このプログラムでは、10万回の加算を行っている。計算機の性能が良くなっているので、さして時間がかからないが、ちょっとの間、PCがフリーズしたような状態になる場合があるので、回数を増やすときは注意すること。

  【演習問題4】 

DIV の値を、
10
100
1000
10000
にして、計算し直してみなさい。
プログラムの主要部分と、実行結果の画面をメール本文にコピーしなさい。



  演算の際の誤差の原因 



1.有効数字の桁数制限
2.10進2進変換の誤差

 10進数を2進数に変換する方法。
 小数の変換法
  10進小数は、2進で正確に表せない







10進数の小数点以下の数を、2進数にする方法。
2倍して、小数点の上に1がきたときは、それを結果とし、1−>0に変えてから繰り返して行く。
どこかで循環するはずである。



 数値シミュレーション

次に、数値シミュレーションの例として、面積を分割して求める方法を掲げる。
ここでは、 y=x という関数と、x軸の間で作られる3角形の面積を、x=0〜1までの間で、分割して、長方形の面積の和として、求める方法を示す。
実際には、数学的には、単純に三角形の面積なので、数値計算するまでもなく、 1/2 となることは、自明なのであるが、ここでは、数値計算の例として掲げる。



分割数を、10,100,1000 と増やしていき、1000万まで増やしていく。細かく分割するほど正確な値になると期待されるかも知れないが、実際には、あまり細かくしすぎると、かえって値が不正確になる。このような点がどこにあるのかを事前に調べることで、どのくらい細かく分割したら、数値シミュレーションが、妥当な値になるかを調べる事が出来る。


  (例題5) 

Project0305


#define A 1
#define STEP 10

int _tmain(int argc, _TCHAR* argv[])
{
    int i;
    float haba, katamuki, menseki , x, y;
      // y= katamuki*x;
    katamuki = A;
    haba = 1.0 / STEP;
    menseki = 0;
    for ( i=1; i<= STEP; i++ )
    {
        x = (float) i / STEP;
        y = katamuki * x;
        menseki += haba * y;
    }
    printf("div= %d, menseki= %f\n" ,STEP, menseki );

    getchar();
    return 0;
}


  【演習問題5】 

分割数を、
10
100
1000
10000
と、増やして行き、 10000000 (1000万) までの結果を求めなさい。

プログラムの主要部分をメール本文にコピーしなさい。
また、もっとも正確な値が出たと思われるときの表示画面のコピーと、
その次に、おかしな値となったと思われるときの表示画面のコピーをメールに貼り付け、
どうして、値を大きくしたら、余計に不正確な値が出るようになったのかについての考察を4〜5行程度で記述してメールを送りなさい。



  (例題6) 

Project0306


#define A 1
#define DIV 1000
    // DIV は、分割数の上限
    // 10倍ずつ分割数を変えて実行してみる。

int _tmain(int argc, _TCHAR* argv[])
{
    int i, step;
    float haba, katamuki, menseki , x, y;
// y= katamuki*x;
    katamuki = A;
    for ( step = 10; step <= DIV; step *= 10 )
    {
        haba = 1.0 / step;
        menseki = 0;
        for ( i=1; i<= step; i++ )
        {
            x = (float) i / step;
            y = katamuki * x;
            menseki += haba * y;
        }
        printf("div= %d, menseki= %f\n" ,step, menseki );
    }

    getchar();
    return 0;
}


  【演習問題6】 
 Project0306 を一部変更し、  y=x*x (xの2乗) という関数について、同様にして、面積を求めてみなさい。
正確な値は、 1/3 (=0.33333…) となるはずである。
プログラムの主要部分と、実行結果の画面をメール本文にコピーしなさい。
何分割したときが、最も精度が良かったか、分割数を答えなさい。


















授業時間があれば、以下の項目についても触れますが、時間を見て、割愛するかも知れません。

変数の有効範囲(関数)

関数へ渡した変数の持ち帰り方(ポインタ渡し)






Powered by FC2ホームページ

inserted by FC2 system