Tag Archives

11 Articles

Linuxで動作するCプログラムのデバッグ環境構築2

by なたで 0 Comments

前回の「Linuxで動作するCプログラムのデバッグ環境構築1」から引き続きます。
今回は、Eclipseを使って、Cのプロジェクトを作ってコンパイルしてみるところまで行ってみたいと思います。

では、さっそく解説をはじめます。
今回は図をふんだんに使って分かりやすく説明します!


プロジェクトを作成しよう

1

Eclipseを開きましたら、左のプロジェクトの部分で右クリックをしましょう。
新規からプロジェクトを追加していきます。

2

今回プロジェクト名は「Sample」と名付けます。
これがプロジェクトエクスプローラに表示されるタイトルになります。
また勉強のために、空のプロジェクトを選択します。
ツールチェーンは、このLinux環境で実行したいのでLinux GCCを選びます。

最後に「完了(F)」ではなく「次へ(N) >」を選んでください。

3

構成を選択できます。
構成というのは、どのようにコンパイルするかといった設定です。
デフォルトでDebugとReleaseがあります。
それぞれデバッグ向き、リリース向きとなっています。
今回勉強のために後で再設定を行う予定ですが、一旦「Debug」にのみチェックをうちましょう。
必ず1つは設定を持つ必要があるため、チェックを打たないという選択肢はありません。
ここで、「完了(F)」を押して、ウィザードを終わらせます。

 


ソースコードを準備しよう

1

今回は、例として下記のようなファイル構成にします。
プロジェクト・エクスプローラ内で右クリックでファイルやフォルダを作成していきましょう。
次のようなフォルダ構成にしてみました。

2

main.c は、オーソドックスに次のようにしました。

#include <stdio.h>

int main(void) {
	printf("Hello world !\n");
	return 0;
}

3

makeファイルも、オーソドックスに次のようにしました。
注意点として、この後デバッグをするために
コンパイルオプションとして「-g」と「-O0」が必ず必要となります。
また、実行ファイル名を「Hello」としました。
設定でデフォルト値に「all」と「clean」が存在しており、これらのコマンドが使用されます。
従って、「all」「clean」が使えるようにしておきましょう。

Target  = main.c
ExeName = Hello

all : $(ExeName)

$(ExeName) : $(Target)
	gcc -g -O0 -Wall -o $(ExeName) $(Target)

clean :
	rm -f $(ExeName)

プロジェクトの設定をしよう

1

先ほどはウィザードで初期設定を行いましたが、ここで改めて設定を構築しなおしていきます。
ファイルメニューから「プロパティ(R)」を選択します。

2

「C/C++ ビルド」で設定が行えます。
最初にウィザードで、構成の「Debug」にチェック入れました。
つまり、この最初に表示されたのが、「Debug」の初期構成となっています。
今回勉強のために、構成の名前を変えてみましょう。「構成の管理…」を押します。

3

Debugを選択して、「名前変更…」で「MyCompile」と変更して「OK」を押してみます。
これで自分で名前をつけた構成となります。

4

EclipseではMakefileを自動生成する機能がありデフォルトで有効となっていますが、
今回は自分でMakefileを管理するため「自動的にMakefileを作成(G)」のチェックを外します。
また、「ビルド・ディレクトリー(D)」でMakefileがおいてあるディレクトリを設定します。
設定が終わったら、「適用(A)」を押しましょう。

5

今度はコンパイルが終わって実行ファイルが作成された後の実行方法の設定を行います。
「実行/デバッグ設定」で、「新規(N)…」を選択します。

6

実行する手法に名前をつけられるので、「MyExecute」としました。
今回、makefileにコンパイル後の実行ファイル名は「Hello」と記載しました。
従って、makefileが実行されると「MyProcess/Hello」という実行ファイルが作成されます。
ここには、その実行ファイルのパスを「C/C++ アプリケーション」に記述します。
ここに記載されたパスが、実行ボタンを押すと実行されるようになるためです。
プロジェクトの欄は、この起動構成プロパティは、何のプロジェクトの起動構成プロパティかという設定です。
今回は、Sampleプロジェクトなので「Sample」とします。
設定を終えたら「OK」を押します。

7

これで設定の完了したので「OK」を押します。


コンパイルをしよう

1

「プロジェクト(P)」メニューの「プロジェクトのビルド(B)」を選びましょう。

2

下のコンソールに「Build Finished」が表示されたら成功です。
左側のプロジェクトエクスプローラーにも、実行ファイルが作成されていることが確認できます。

 


実行とデバッグをしよう

1

緑色の▲ボタンを押すと、実行できます。簡単でしょ!
コンソールにはしっかり「Hello world !」と表示されます。

2

虫(bug)のマークを押すとデバッグが行えます。

デバッグを中止したいときは、■を押して停止させます。
元のウィンドウ構成に戻すためには「ウィンドウ(W)」の
「パースペクティブを開く(O)」から「C/C++」を選択します。
Eclipseでは、ウィンドウやコントロールの位置などのプリセットをパースペクティブと呼びます。
この操作を行うことで、デバッグ用のパースペクティブから元に戻すことが可能です。


おまけ

1

今回は、変更しませんでしたが「振る舞い」でMakefileの引数も設定できます。
デフォルトは先ほど説明したように、「all」と「clean」になっています。

2

もし、ソースコード内でincludeするときに、別のライブラリのヘッダを参照している場合は、
下記のインクルード設定で設定を行えます。
ただし、今回のようにMakefileを手動設定にしている時点で、
ここの設定が誤っていてもコンパイルと実行は可能です。
ただ設定が誤っていると、ソースコードエディタで注意がされてしまいます。


おわりに

おつかれさまです。
いろいろEclipseをいじりながら無事を書き終わることができました。

では、また!

Linuxで動作するCプログラムのデバッグ環境構築1

こんにちはー!

今回は、Linuxで動作するCのデバッグ環境の整え方をお話しします。

皆さんは、Raspberry PiとかのLinux上で動作するプログラムを作るときにどうしていますか、
Raspberry Pi 上で make して色々プログラムを作っていくのもいいですが、
コンパイルの速度が遅いですし、効率も落ちてしまいます。

では、どうすればいいのか。
Windows環境で、Linux上のプログラムを作ってテストしたいと思った時に
一番簡単なのは、仮想環境でLinuxを起動させてPCでmakeしていく方法です。
ただ、仮想環境は重たい切実な問題があります。

そこで、今回はできる限り軽い仮想環境を作って
Cプログラムをデバッグ出来る方法まで説明します。


作戦

まず、Linuxといってもたくさん派生しており、色々な環境があります。
そのため、かるーいOSもあるはずです。

Linux

ベースとなるLinuxを考えましょう。

Linuxの種類

OSの説明です。
私が知っている情報は、この5つしかありません(認識不足)

RedHat系

RHEL(Red Hat Enterprise Linux) / CentOS / Fedora 等のサーバー向けのOSです。
RHEL は、有料のOSであるため、サポートや情報が多く、サーバーに使用されることがおおいです。
CentOS は、RHEL の無料版です。サポートが受けられませんが、信頼性が高い RHEL を使えるような形です。
Fedora は、新しい機能を追加していくOSです。最新の機能を試したい向けです。

Debian GNU/Linux

フリーのOSを作成する団体Debianが開発したLinuxベースのOS。
搭載アプリは安定版のため少し古いですが、信頼性は高くネットの情報も多いです。
Debianから多くのOSが派生しており、Raspbianなど組み込み向けLinuxも作られることが多いです。

Ubuntu

Debian ベースのOSで、個人向けだと有名なOSです。
デスクトップPC向けに様々なカスタマイズがされているOSです。
利用者が多いため非常に情報量が多いです。

Arch Linux

必要最低限の物しか入っておらず全てを自分でカスタマイズする超上級者向けOS

BSD系

上の3種類と違ってLinuxではありませんが、よく耳にするOSなので紹介します。
Linux系のOSは、総じてGPLなのですが、BSD系のOSは、BSDライセンスとなります。
製品に組み込む際にBSDがよいといった場合に選ばれるOSです。

どのLinuxを選択するのか

結論からいいますと、Debian を今回選択しました。
というのもカスタイマイズされた Ubuntu は、有名なOSで情報量も多いのですが、
デバッグ環境だけに限れば、必要がないアプリがインストールされます。

そして、Debianは、Raspbianなど組み込みLinuxでベースとされることが多いため、
Debian での環境で何か開発すれば、それを他の組み込みに持っていくのが楽かと考えたためです。
Armadillo とかも Debian が動いています。

デスクトップ環境

Linuxを知っている方ならご存知ですが、デスクトップ環境は好きなものが選べます。
ここで選ぶものによって、非力なPCでは重たくなってしまいます。
Debian では、インストール中にいくつかデスクトップ環境を選ぶことができます。

デスクトップ環境の種類

Unity, GNOME

下記のに比べるとグラフィカルでUIが洗練されていますが、
その分メモリやパワーを使用します。
Ubuntu のデスクトップ環境を選ぶと標準で利用されます。
※Unity は Debian では選べません。

Xfce

当初、これを検討していました。
歴史があるデスクトップ環境のためか、
現在のPCでは軽快に動作する特徴を持っています。

LXDE

これは歴史は浅いですが、処理能力の低いPC向けに作られた環境です。
軽快に動作することを目的としています。

どのデスクトップ環境を選択するのか

今回は、LXDEを選択します。
目的が軽さというのがよいです。
デバッグ環境のみに使用するという目的とマッチしています。

デバッグ用ツール

ここはあんまり調査していませんでした。
Windows でもよく使用される Eclipse を使用したいと思います。


開発環境を整えよう

全てはかきませんが、流れを説明します。

前準備

次の環境を用意します。

・Oracle VM VirtualBox
仮想環境です。今回は 5.2.4 を使用します。
仮想環境は、スナップショットという機能を使うことで誤った設定をしても以前の段階に戻れるためおススメです。
VMware という選択肢もありますが、商用利用に制限があるためこちらを選択します。

・Debian
Debianのサイト」で
「Debian を入手する」→「ネットワークインストール」→「最小の CD を使って、ネットワークインストールする」を選択します。
「netinst CD イメージ (約 150-300 MB ですがアーキテクチャよって変わります)」の中にある
「i386」を選択して「debian-9.3.0-i386-netinst.iso」をダウンロードします。(x86用OSを想定)

・ネットワーク
プロクシなしで、ネットワークに接続できる環境を準備します。

VMで仮想環境を作成

新規作成する

1. 「Oracle VM VirtualBox」を起動して、「新規」を選択します。
2. タイプはLinux、バージョンはDebian(32-bit)を設定します
3. メモリサイズは緑の範囲で出来る限り多くとります。
4. ハードディスクでは「仮想ハードディスクを作成する」を選び「作成」。
5. ハードディスクはVDIを選択。サイズは可変サイズを選択
6. 「ファイルの場所とサイズ」になったら、HDDファイルの場所を設定してください。
場所やファイル名は変更がすこし大変なので注意。
また、ハードディスクのサイズは120GBほどに設定しましょう。
こちらは、変更が非常に困難なので極力大きめにします。

設定変更する

新規作成すると左側の領域に作成した仮想環境が表示されます。
これを選択した状態で「設定」を選ぶと、より設定変更が行えます。
次のように設定していきます。
1. 一般の高度で、クリップボードの共有を双方向にする
2. システムのプロセッサーで、CPU数を増やす
3. オーディオとUSBは無効化する(使用しないため)
4. ネットワークはNATとなっていること(外部にアクセスするため)
よくやる設定はこんなものです。

OSのインストール

1. 作成したプロジェクトを選び、起動を選びます。
2. 最初にISOを選択する画面が表示されるため「debian-9.3.0-i386-netinst.iso」を選択します
3. インストール方法は、グラフィカルじゃないインストール方法 Install を選択します。
※操作は、矢印キーとスペースキーで選択し、エンターキーで決定です。
4. あとは適当に進めていきます。途中で言語の設定が出た場合は「日本」を選びましょう
5. 途中で、初期インストールの設定が出たら、「LXDE」と「SSH サーバー」を選びましょう。
6. あとは適当に進めていけばOSのインストールが完了です。

OSの初期設定を行う

ターミナルを駆使して設定していきます。
スーパーユーザー権限で行うことが多いので「sudo su -」と入力しておくと楽です。

パスを英語化する

「デスクトップ」とかフォルダパスに
日本語が入っているとやり辛いので最初にやります。
1. xdg-user-dirs-gtk をインストールして、updateを実行します。

sudo apt-get install xdg-user-dirs-gtk
LANG=C xdg-user-dirs-gtk-update

Guest Additions CDをインストールする

Guest Additions CD とは、
VM上で動くOSでインストールすることで、
共有フォルダや画面解像度変更、クリップボード共有など
色々と使いやすくなるツールです。
1. VMのメニューバーにある「デバイス」→「Guest Additions CDイメージの挿入…」
2. CDが認識されます。このままだと実行できないので、一旦ディスクのフォルダをデスクトップへコピー(cdrom というフォルダにします)
3. 実行する前に、実行に必要なソフトをインストールします。

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install dkms
sudo apt-get install build-essential module-assistant 
sudo apt-get install linux-headers-686 linux-headers-4.9.0-4-686
※Linuxの部分は、DebianのLinuxカーネルのバージョンに依存する
そのため、下記のVBoxLinuxAdditions.runを実行すると
必要なインストールファイルが表示されるのでそれを参考にするよい

4. Guest Additionsをインストールして再起動

cd /home/***/Desktop/cdrom
sudo ./VBoxLinuxAdditions.run
sudo reboot

※もし「VBoxLinuxAdditions.run」でエラーが出た場合は表示内容をインストールする。
それでも駄目なら、VirtualBoxを最新版にする。
(Debianのリリース日より新しいバージョンだと確実。)
5. Guest Additions CD を取り出す
VMのメニューバーにある「デバイス」→「光学ドライブ」から除去的なのを選ぶ

共有フォルダの設定

Windows上とファイルの行き来する際に、共有フォルダを作ると便利です
1. VMのメニューバーにある「デバイス」→「共有フォルダー設定…」
2. フォルダの追加ボタンで、ダイアログを表示させる
3. フォルダのパスを入力する部分で、右端の▼を押して「その他…」を選び、フォルダを選択
4. 「自動マウント」と「永続化する」を選びOKを押す
5. OSを再起動
6. 共有フォルダ「/media/sf_***」への権限を付与

sudo gpasswd -a ユーザ名 vboxsf

7. OSを再起動

不要なファイルを削除する

いらないファイルは予め削除しちゃいましょう。
下記のコマンドで、オフィスツールや画像編集ソフトをアンインストールできます。

sudo apt remove libreoffice*
sudo apt remove gimp*
sudo apt remove firefox*
sudo apt-get autoremove

あると便利なソフトをインストールする

コンパイルに必要なmakeやファイルサーバーのsambaは必須!よく使うので。

sudo apt-get install make
以下、おすすめ
sudo apt-get install samba
sudo apt-get install curl
sudo apt-get install aptitude
sudo apt-get install php

サーバーを自動起動させる

あとは、SSHやSAMBAの設定を好みでしておきましょう。

Eclipseのインストール

Eclipseをインストールしていきます。
ここからは「Synaptic パッケージマネージャ」を使用していきます。
「Synaptic パッケージマネージャ」というのは、アプリストアみたいなものです。
1. スタートメニューから「設定」→「Synaptic パッケージマネージャ」
2. 次の4つを選択した後に適用を押し、インストールします。
・eclipse(本体)
・eclipse-cdt(C/C++開発用)
・pleiades(eclipseの日本語化)
・gdb(gccのデバッグ支援機能)
3. スタートメニューから「プログラミング」→「eclipse」を選択して、起動させて終了させる。
4. 日本語化のために次のように「eclipse.ini 」を開く

sudo nano /usr/lib/eclipse/eclipse.ini

5. 最終行に次の1行を追加して上書きする。

-javaagent:/usr/lib/eclipse/plugins/jp.sourceforge.mergedoc.pleiades/pleiades.jar

6. ターミナルで次のコマンドをうつ。

eclipse -clean

7. これでC言語でデバッグ環境の準備を終えました。


デバッグしてみよう

ここからは、通常のeclipseの使い方と同じです。
ここについては私がもっと詳しくなったら記述したいと思います。

Linuxで動作するCプログラムのデバッグ環境構築2」へつづく


おまけ

配布用のサイズを縮小する

仮想環境で作るメリットとして、
一旦作ってしまえば、面倒な環境構築無しで、他のパソコンでもすぐに実行できるということがあります。
ただ1つ問題がありまして、この仮想環境のファイルシステムはどんどん大きくなっていきます。
可変サイズという設定にしているので、
使用しなくなったらサイズが減ると思っているかもしれませんが減りません。

配布する際にこのようにファイルサイズが巨大になったまま配布すると扱いが大変なので、
縮小させることを行います。
1. ゴミ箱は空にしておく
2. スナップショットを使用しているならば、統合するためにスナップショットを全て統合させる。
3. 仮想OS上(Debian)で次のコマンドをうつ

sudo dd if=/dev/zero of=zero bs=4k; rm zero

4. 仮想OSをシャットダウン
5. Windows上で管理者権限でコマンドプロンプトを起動させる
6. VirtualBox のインストール先にカレントディレクトリを移動させる
5. 「VBoxManage list hdds」で縮小させるVDIのUUIDを調査する
6. 「VBoxManage modifyhd [UUID] –compact」で縮小させる
7. 圧縮ソフト7-zipを使って、7z形式で高圧縮させる


常にスーパーユーザーになる

あまりおすすめしませんが、
重要なコマンドを続けて実行する場合は常にスーパーユーザーになると楽です。

sudo su -

と実行し、自分のログインパスワードをうてばrootになれます。

なお、ifconfig等のコマンドは、スーパーユーザーでなければ、
パスが通っていないため実行できません。

/sbin/ifconfig
ifconfigを使わないのであれば、以下でも実行可能。
ip a show

もし、どのユーザーでもifconfigを使いたいのであれば、

sudo nano /etc/profile

if [ "`id -u`" -eq 0 ]; then
  PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
else
  PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
fi

のスーパーユーザーかどうかif分判定しているelseの方のパスの設定を

  PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/sbin"

とする必要があります。

セカンドライフでプログラム1(書き方がC言語に似ているよ編)

コンニチハ。
最近、セカンドライフやりだした。なたでです。
たぶん以前やったのが、
今から6年前の2008年5月の「ネトゲ日記-1」ですね。

で、なぜやりだしたかというと、
前々からVRに興味があるという話をしていたと思いますが、
次世代HMD?である話題もちきりの「Occlusion Rift」が、セカンドライフで対応したのです。
(→ニュース Second Lifeの仮想空間、Oculus Riftでの移動が可能に
ということは、日本では失敗に終わった?セカンドライフが、Occlusion Riftの発売によって、再燃する可能性が!
しないかもしれませんが。絶対しないな。

とにかくそんなこんなでセカンドライフを、ちょっとやり始めたわけです。
さて、そんなセカンドライフなのですが、何が目的?とよくあるのですが、
基本的には、人と話し合ったり、触れ合ったりするのが楽しいです。

今回は、そんな話はおいておいて、
こんなセカンドライフで利用できるプログラミング言語 LSL (Linden Scripting Language) について、
紹介をしていきたいと思います。

このLSL、正直C言語と似てます。
文字列や配列とか考えると、JavaScriptに近いかもしれません。
あと、イベントがあります。イベントが発生するとこの関数が呼ばれるといった感じで。

話が戻りますが、どこが似ているのかというと、
たとえばコメントは同じです。

/*
コメント
*/
// コメント

ほかに、関数の作り方も。

myfunc() {
}

ちなみに、型はこんな感じです。
型は、宣言する必要があります。
すべて値型で、参照型ではありません。(値型、参照型とは

整数型は、integer
実数型は、float
文字列型は、string

それで、ほかに特殊な型がありまして、

  • key
  • list
  • vector
  • rotation

上の4つです。

・key
セカンドライフでは、すべてのものにUUIDという個別のIDが割り振られます。
たとえば、自分のアバターにもUUIDがありますし、
アップロードした画像1枚1枚、音声1つ1つ、
たった今作成したオブジェクト(プリム)にもUUIDが存在します。
そんなUUIDを記録するためのがこのキー型です。
中身は文字列らしいですが、key型として存在してます。
このkey型は組み込み関数の引数として、よく使用したりします。

・list
配列です。セカンドライフでは配列はこれです。
正直、この配列はJavaScriptみたいです。

list x = [ 1, 2, 3.4, "sss "];

のように配列に代入できるのですが、
list以外の型なら何でも入れることができます。
ただ、1つ癖がありまして、x[3]とか、添え字が使えないんです。
ということは、取得が少々面倒です。たぶんこれは各自調べるか、後々別の回で。

・vector
x, y, z の3つの値を持ちます。
セカンドライフの座標情報とか、大きさとかそういうのに使われます。

・rotation
x, y, z, s の4つの値を持ちます。
いわゆる、3D空間の回転でお馴染みのクォータニオン(四元数)です。
2D空間の座標を複素数で表せられれます。
四元数というのは、この複素数を3D空間へ拡張したようなものです。
各自が好きなオブジェクトを作れるセカンドライフでは、
オイラー角で管理すると起こるジンバルロックの問題youtube)があるため、
四元数で回転を管理しています。
中の値を見ても、どのような角度になっているか、正直わからないと思いますが、
分かりやすいオイラー角からrotation型へ変換する関数があります。

・おまけ json
実は JSON も扱えます。
ただし、string として扱っていきます。つまり型ではないです。
JSON自体が文字列なのでstringであっているといえばあっていますが。
キーワードだけおいておきます。各自調べてください。残念ながら使い方まで調べてません。
llJsonValueType
llJsonGetValue
llJsonSetValue

以上、型の紹介は終わりです。
matrix型がないので、回転移動の管理はちょっと面倒そうですね。
void型も、boolean型もないですよ。
boolean型は、C言語と同じ、0(false) か 0以外(true) として扱います。
ちなみに、これもすべて値型です。関数に渡すときは値渡しです。
配列が値渡しなのが驚きですね。

それで型を使って関数を書くと、
こんな感じに、まあほとんどC言語ですね。

integer abc(float x, float y) {
	integer c = 3;
	return((integer)(x + y) + c);
}

なお、型変換は上のようにC言語と同じ書き方でキャストできます。
気になる点としては、暗黙の型変換です。
というのは、文字列を入れる関数に、ベクトル型を入れるとエラーがでます。
まあ、当たり前かもしれませんが、JavaScriptに毒されていると違和感があります。
型変換については、/Typecast/ja を見るといいです。
あと、関数のオーバーロードはできません。

制御文は、C言語と同じ書き方で、
for/do/while文、if/else文があります。
gotoもあります。これはjump文という命令名となっていますが。

jump label;
@label;

break文、continue文がないので、jump文を使用しましょう。
なお、switch文もないようです。

さらに、スコープはC言語と同一で、
ブロックを好きなところに書いて、スコープを限定できます。

{
	integer x = 1;
	{
		integer x = 2;
	}
}

とりあえず、書き方の基礎的なものの話はこれで終わります。
興味あったら始めてみてはどうでしょうか。
プログラム触ったことがある方なら、すんなり入れると思います。はい。

ちなみに、次のサイトよく使用します。

それぞれの関数の具体的な内容や、使用例が書いてあるサイトです。
LSL ポータル

目的別で整理されているサイトです。
LSLめも

指定した範囲の乱数を作成したい(実戦編)

みなさん、こんにちは。
さて、前回前々回にわたって、
指定した範囲の乱数の作成の仕方を書きました。
改めて奥が深いですね。

読み忘れた方のためにリンクしますっ(^o^)
指定した範囲の乱数を作成したい(前編)
指定した範囲の乱数を作成したい(後編)
指定した範囲の乱数を作成したい(実戦編)←いまここっ!


これまでの話では、
小さなより精度が低い乱数発生器を使用していたため、
実用上は、そんなに関係ないんじゃないかな。
と思う方も多いかもしれません。

今回の話はかなり短いですが、
実際の実用例を考えて、
乱数の落とし穴にはまっていきたいと思います。(^o^)丿

あなたは、ネットワークゲームを設計しています。
モンスターを倒すと、レアアイテムを落とします。
確率は、0.01%刻みで設定できるようにしたいです。
言語は、VC++、またはHSPを使用することとします。

ほら、ちょっと本当にありそうな雰囲気になってきましたよね。
さて、どのように設計するでしょうか、
VC++の乱数発生器だったら、みんな使ってるし大丈夫だろう。
乱数作成の高速化も考えて、 x = rand() % 10000 こんな感じにして、
で、if(x < 1000) で 10% みたいに計算しちゃおう。

では、実際にコードを書いてみましょう。
とりあえず、HSPが好きなので、HSPで書きます。
まず、 HSP には rnd(n) という関数があるのですが、
これはCで rand() % n という文章にすぎません。

#define multiplier 214013
#define addend 2531011

x = 1

goto *start

#defcfunc rnd2 int s
	x = x * multiplier + addend
	return (((x >> 16) & 0x7FFF) \ s)

*start

mes "HSPの rnd(n) は、余りを使用して乱数を切り出している。"
mes "HSPの rnd -> " + rnd(100) + " は"
mes "上記の rnd2 -> " + rnd2(100) + " と同じ。"

上記をふまえて、
乱数の値がどのように分布するか度数分布表を作ってみましょう。

size = 100000
dim bin, 10

repeat size
	x = rnd(10000)
	bin(int(x / 1000))++	
loop

repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

実行すると、

範囲回数
[ 0, 1000)12401
[1000, 2000)12176
[2000, 3000)11618
[3000, 4000)9079
[4000, 5000)9088
[5000, 6000)9107
[6000, 7000)9150
[7000, 8000)9048
[8000, 9000)9316
[9000,10000)9017

つまり、10%を作るつもりが、12%になってしまいます!!
さらに、if(rnd(10000) < 3000) で 30.00% みたいに計算しちゃおうと思うと 36%になります。

棒グラフを作ると分かりやすい。
histogram

そんなこんなで、
今回は、ちょっと身近に感じそうな話を作ってみました。
みなさん、乱数には十分に気を付けましょう!

ちなみに、HSPの機能の中でより正しい乱数を作りたい場合は、
これまでの話をふまえると下のような感じ。(色々と追加しました。)

#module

#defcfunc rnd15 int s
	// 15ビットの実数の乱数を作成
	return int(((double(rnd(0x8000))) / 0x8000) * s)

#defcfunc rnd30 int s
	// 30ビットの実数の乱数を作成
	return int(((32768.0 * rnd(0x8000) + rnd(0x8000)) / 0x40000000) * s)

#defcfunc rnd52 int s, local a
	// 52ビットの実数の乱数を作成
	a = double(rnd(0x8000))
	repeat 3
		a *= 32768
		a += rnd(0x8000)
	loop
	return int(a / 1152921504606846976.0 * s)

#defcfunc rnd4 int s, local a, local b
	// 繰り返して範囲外は捨てる
	repeat
		a = rnd(0x8000)
		b = a \ s
		if(a - b + s <= 0x8000) {
			break
		}
	loop
	return b

#global

font "MS ゴシック", 12
pos 5, 5

// 作成する乱数の範囲によって、
// 乱数のビットを大きいものを選ぶか、
// 上記の rnd4() を利用するといいです。

randomize
size = 1000000

mes "通常版"
dim bin, 10
repeat size
	x = rnd(10000)
	bin(int(x / 1000))++	
loop
repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

mes "工夫したもの"
dim bin, 10
repeat size
	x = rnd15(10000)
	bin(int(x / 1000))++	
loop
repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

mes "誤差が少ない"
dim bin, 10
repeat size
	x = rnd30(10000)
	bin(int(x / 1000))++	
loop
repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

pos 200, 5

mes "最も誤差が少ない"
dim bin, 10
repeat size
	x = rnd52(10000)
	bin(int(x / 1000))++	
loop
repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

mes "正しい乱数"
dim bin, 10
repeat size
	x = rnd4(10000)
	bin(int(x / 1000))++	
loop
repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

上記の「正しい乱数」のアルゴリズムについては、下で紹介していますが、前編から読むとより勉強になるかもしれません。
指定した範囲の乱数を作成したい(後編)

指定した範囲の乱数を作成したい(後編)

みなさん、こんにちは。
今日は、昨日の続き、「指定した範囲の乱数を作りたい」を続きに話していきたいと思います。
というか、解答編みたいな感じです。

指定した範囲の乱数を作成したい(前編)
指定した範囲の乱数を作成したい(後編)←いまここっ!
指定した範囲の乱数を作成したい(実戦編)

それでは、ちょっとおさらいをしながら進めていきます。
今回は、3ビット(0, 1, 2, 3, 4, 5, 6, 7 の計8種類の数値)の
一様分布の値を出力する乱数生成器random8()を利用して、
0~2の3種類の数字を出力してみましょう。

この3ビットの乱数生成器random8()の性能を表にまとめます。

乱数値01234567
確率1/81/81/81/81/81/81/81/8

それでは、前回と同じように3で割った余りを使用します。
y = random8() mod 3

乱数に対して、3で割った余りを求めて

乱数値01234567
y (3で割った余り)01201201

確率をまとめると、

y012
確率3/83/82/8

やはり、前回の2ビット乱数発生器と同様にだめでした。
まあ、ただ2ビットの乱数発生器に比べて誤差は減っています。
乱数発生器のビット数が大きいほど誤差は少なくなるようですね。

さて、では3ビットの乱数生成器では、
当確率で、0から2の数字を出力することができないのでしょうか。
いいえ、出来ます。

気付いている人も多いかと思いますが、
毎回、問題になっているのは「はみ出している」部分なのです。
「はみ出している」というのは、下記の黄色の箇所です。

乱数値01234567
y (3で割った余り)01201201

つまり、3ビットの乱数生成器で0~7の値が出力されるが、
そのうち6か、7が出た場合は、
もう1度、3ビットの乱数生成器で出力するのです。

もちろん、計算コストが上がります。
例えば、3ビットの乱数生成器では

乱数0~56~7
確率6/82/8
利用するしない

それぞれの数字を出力する確率は上記のようになり、
6~7という数字が、2/8の確率で出力され、
もう1度計算する必要が出てくるのです。
しかも、次の乱数も、6~7なら、また乱数作成の計算が必要です((+_+))

さて、次はアルゴリズムの話に移ります。
出力された値が、6~7かどうかはどのように判断したらいいでしょうか。
上記のように、「0~7の8種出力する乱数から、0~2までの3種類の乱数が欲しい!」
と決め打ちされているならば簡単ですが、
乱数のビット数、いくつ未満の数字が欲しいか、いくつもの場合が考えられます。
でも、色々な場合とか考えると、大変なので、やっぱり簡単な乱数で勉強しましょう。

とりあえず、次の表を見てください。

乱数値 a01234567
b=a mod301201201
c = a – b00033366
d = c + 333366699
e = d > 8falsefalsefalsefalsefalsefalsetruetrue

ピーン(^o^)っときませんか?
上の表に出てきている
3は、必要な乱数の種類、0~2までの3種類の3です。
8は、3ビットの乱数発生器の性能、2の3乗の8です。
もうお分かり頂けたと思います。

ちょっと、C言語で上記の考え方で、乱数を作ってみましょう。
C言語では、乱数をrand()で出力するのですが、
この出力する範囲は「0~RAND_MAX」となり、「RAND_MAX + 1」種類の数字を出力します。

int TRUE = 1;
unsigned long n = 3; // 欲しい乱数の種類
unsigned long y = 0; // 出力値
unsigned long random = 0;
while(TRUE) {
   random = rand();
   y = random % n;
   if( ( random - y + n ) > (RAND_MAX + 1) ) {
	  continue;
   }
   break;
}

まあ、とりあえず説明にあったような流れで、無理やり書いて、
上記のようなコードになりました。
do文を利用するともっとすっきり書けます。

実は1つだけ落とし穴があります。
それは、RAND_MAXの値が非常に大きい場合や、
( random – y + n )の値が実は巨大な数字の場合です。
signed long などで計算している場合は、負の値になってしまいますし
unsigned longを使用していたとしても、オーバーフローして、小さな値になってしまう可能性があります。

例として、「signed short」16ビットの正負の範囲を表す整数値を使用しており
さらに、RAND_MAX は 0x7fff の場合、RAND_MAX + 1 は -32768 となり負になってしまいます。
そのため、IF文の結果が思った結果と異なる場合があります。
アルゴリズム自体は分かったと思いますので、
上記の点だけ注意して、値がおかしくならないように設計して下さい。

さて、最後なのですが、
JavaScriptのように、0以上、1未満の数字を出力する
Math.random()しかない場合は、どうすればいいでしょうか。
この時も、前編で話したように同様の問題を抱えています。

この場合も、一度整数に戻すしかありません。
疑似乱数は、通常ビット単位で作成されます。
(これまでで話したように、任意の数までの乱数を作るのは面倒だからです。)
そのため、Math.random() を作成している元となる乱数も
元々は、あるビット長の整数を元にしていると思われます。
この場合は、Math.floor(Math.random() * 0x8000) とすれば、
0から32768未満の整数値を、均等な確率で出力します。
あとは、この整数値を利用して、上のC言語で説明したように作成すれば
しっかりとした乱数が作成できます。

ちなみに、Javaの場合は、RandomクラスnextInt(n)を使用すれば、問題ありません。
さすがJavaです。速度も速いですし。
とりあえず、「指定した範囲の乱数を作成したい」はここで終わりです。

話は、少し変わりますが乱数は作成する方法がたくさん考案されてます。
疑似乱数の質とか考えると、よく利用される線形合同法はマズいです。
利用する目的にもよると思いますが、
乱数の種類などを勉強しておくといいかもしれません。
メルセンヌ・ツイスタは、日本人が開発したもので、乱数の質が高いです。
(ただ、MTは、簡単に自分の手で実装できないのがデメリットがあります><)

続きは、HSPを使ったより実践的な話です。
指定した範囲の乱数を作成したい(実戦編)

%d人のブロガーが「いいね」をつけました。