LinuxでAndroidSDKの更新をするとき、/tmpの容量不足で失敗する事への対処方法
最近のLinuxディストリビューションは/tmpに物理メモリを割り当てていることがあるようですが、例えば当方が使用しているFedora26は、標準で物理メモリの半分をtmpfsとして/tmpにマウントしているようです。
当方のPCは物理メモリが8Gしか無いので、/tmpは4Gしか使えないことになります。これで、AndroidSDKの更新(特にNDKの更新)をすると、/tmpを使い切ってしまい、「ディスクの空きが足りない」的な英語のエラーメッセージと共に失敗します。この現象の対策をしてみます。
対策
/tmpへのtmpfsのマウントはsystemdがやっているのですが、下手にsystemdのユニットをいじって起動不能になるのは面倒なので、systemdには手を出さないことにします。
javaの一時ファイル用ディレクトリを変更する
というわけで、javaの挙動を変えていきます。java実行環境はシステムプロパティ「java.io.tmpdir」を一時ファイル記憶場所とするので、-Dオプションでこのプロパティを変えてしまえば、任意のディレクトリを一時ファイル記憶場所にできます。
java "-Djava.io.tmpdir=/path/to/tmp" -jar hogehoge.jar
では、どうやってAndroidStudioやsdkmanagerにこのオプションを与えるかですが、まず、AndroidStudioはシェルスクリプトpath/to/AndroidStudio/bin/studio.shを、sdkmanagerはシェルスクリプトpath/to/AndroidSDK/tools/bin/sdkmanagerをそれぞれ実行することで起動しています。これらスクリプトでjavaコマンドを起動しているのですが、オプションを与えるためにこれらスクリプトを書き換えてしまうと、逆に/tmpを使いたいときに、また戻さないといけなくなります。
そこで、環境変数「_JAVA_OPTIONS」を使います。例えば、AndroidStudioを起動する場合はこうします。
export _JAVA_OPTIONS="-Djava.io.tmpdir=/path/to/tmp" #studio.shを含むディレクトリへのパスは通してあるものとします。 studio.sh
スクリプトで自動化
毎度ディレクトリを作ったり、環境変数をexportしたりするのは面倒なのでスクリプト化します。
この一連の操作を実行するスクリプトを作成します。
ロックファイルは$HOME/.as_lockとし、一時ファイルディレクトリを$HOME/.as_java_tmpとします。PowerShell Core向けスクリプトを作成します。(えっ!bash?だれですかそれ?)
AndroidStudioWithTemporaryDirectoryOnDisk.ps1
#!/usr/bin/pwsh -F Set-Location $env:HOME try{ if(Test-Path AndroidStudioWithTemporaryDirectoryOnDisk_Error_Log.txt){ Remove-Item AndroidStudioWithTemporaryDirectoryOnDisk_Error_Log.txt } [System.IO.FileStream]$lock_file = New-Object System.IO.FileStream (".as_lock", [System.IO.FileMode]::Create) $lock_file.Lock(0, 1) if(!(Test-Path .as_java_tmp)){ New-Item .as_java_tmp -ItemType Directory } if(!((Get-Item .as_java_tmp -Force).PSIsContainer)){ throw "./.as_java_tmp is not directory" } Set-Item env:_JAVA_OPTIONS -value ($env:_JAVA_OPTIONS + " -Djava.io.tmpdir=" + $env:HOME + "/.as_java_tmp") studio.sh Remove-Item .as_java_tmp -Force -Recurse $lock_file.Unlock(0, 1) } catch{ Write-Host $Error | Out-File AndroidStudioWithTemporaryDirectoryOnDisk_Error_Log.txt }
このスクリプトを$HOME/.local/binなどPATHの通った場所に置き、実行権を与えておけば、いつでも簡単に$HOME/.as_java_tmpを一時ファイルディレクトリにしてAndroidStudioを開始できます。
マウス操作だけで開けるようにする
どうせなら、アプリケーションメニューやデスクトップ上のアイコンから上記スクリプトを実行できると楽なので、.desktopファイルを作っていきます。
アプリケーションメニューのプログラミングカテゴリにあるAndroidStudioの項目をデスクトップに追加し、テキストエディタで開きます。項目が無い場合、AndroidStudioでTools->Create Desktop Entryを選択して作成できます。開いたらこれを改造します。
#!/usr/bin/env xdg-open [Desktop Entry] Version=1.0 Type=Application Name=Android Studio With Disk Temporary Directory Icon=/opt/AndroidStudio/bin/studio.png Exec=AndroidStudioWithTemporaryDirectoryOnDisk.ps1 Comment=The Drive to Develop Categories=Development;IDE; Terminal=false StartupWMClass=jetbrains-studio
Nameをそれっぽい名前にし、Execを先程のスクリプトにします。IconはAndroidStudioのインストール先によって異なるので違うかもしれません。
適当に名前を付けて(ここではjetbrains-studio-disk-tmp.desktop)デスクトップに保存します。これを/usr/local/share/applicationsか$HOME/.local/share/applicationsに入れておけばアプリケーションメニューにも追加されます。
.NET CoreとGtkSharpでGUIアプリを作ってみる[SDLでオーディオ]
前回の記事で.NET CoreとGtkSharpで何かしらGUIアプリを作ってみたわけですが、アラームがならないキッチンタイマーというどうしようのないものでした。
geeksinhitachiprovince.hatenablog.comというわけで、SDL2の補助ライブラリ、SDL2_mixerを使って音楽ファイルを演奏できるようにし、それをアラームということにしてみます。
SDLについては本家ページの他、Wikipediaをご覧ください。
SDLを使用可能にする
Cライブラリのインストール
最近のFedoraの場合
sudo dnf install SDL2_mixer-devel
Windowsの場合
前回の記事同様MSYS2を利用します。
pacman -S mingw-w64-x86_64-SDL2_mixer
.NET CoreアプリからSDL2_mixerを使えるようにする
NuGetでSDL2のC#バインディングを探したのですが、Mono/.NET Framework向けだったり、mixerが使えるのかわからなかったりしたので、自分でバインディングを書くことにしました。今回必要な関数は少ないので簡単です。
SDL2
namespace GIHPLib { public class SDL2 { public const uint /* Constants for init */ SDL_INIT_TIMER = 0x00000001u, SDL_INIT_AUDIO = 0x00000010u, SDL_INIT_VIDEO = 0x00000020u /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */, SDL_INIT_JOYSTICK = 0x00000200u /**< SDL_INIT_JOYSTICK implies SDL_INIT_EVENTS */, SDL_INIT_HAPTIC = 0x00001000u, SDL_INIT_GAMECONTROLLER = 0x00002000u /**< SDL_INIT_GAMECONTROLLER implies SDL_INIT_JOYSTICK */, SDL_INIT_EVENTS = 0x00004000u, SDL_INIT_SENSOR = 0x00008000u, SDL_INIT_NOPARACHUTE = 0x00100000u /**< compatibility; this flag is ignored. */, SDL_INIT_EVERYTHING = SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER | SDL_INIT_SENSOR ; public const int /* Constants for audio format */ AUDIO_U8 = 0x0008 /**< Unsigned 8-bit samples */, AUDIO_S8 = 0x8008 /**< Signed 8-bit samples */, AUDIO_U16LSB = 0x0010 /**< Unsigned 16-bit samples */, AUDIO_S16LSB = 0x8010 /**< Signed 16-bit samples */, AUDIO_U16MSB = 0x1010 /**< As above, but big-endian byte order */, AUDIO_S16MSB = 0x9010 /**< As above, but big-endian byte order */, AUDIO_U16 = AUDIO_U16LSB, AUDIO_S16 = AUDIO_S16LSB, AUDIO_S32LSB = 0x8020 /**< 32-bit integer samples */, AUDIO_S32MSB = 0x9020 /**< As above, but big-endian byte order */, AUDIO_S32 = AUDIO_S32LSB, AUDIO_F32LSB = 0x8120 /**< 32-bit floating point samples */, AUDIO_F32MSB = 0x9120 /**< As above, but big-endian byte order */, AUDIO_F32 = AUDIO_F32LSB ; public static readonly int AUDIO_U16SYS, AUDIO_S16SYS, AUDIO_S32SYS, AUDIO_F32SYS ; static SDL2(){ if(System.BitConverter.IsLittleEndian){ AUDIO_U16SYS = AUDIO_U16LSB; AUDIO_S16SYS = AUDIO_S16LSB; AUDIO_S32SYS = AUDIO_S32LSB; AUDIO_F32SYS = AUDIO_F32LSB; } else{ AUDIO_U16SYS = AUDIO_U16MSB; AUDIO_S16SYS = AUDIO_S16MSB; AUDIO_S32SYS = AUDIO_S32MSB; AUDIO_F32SYS = AUDIO_F32MSB; } } [System.Runtime.InteropServices.DllImport("SDL2", EntryPoint="SDL_Init")] public static extern int Init(uint flags); [System.Runtime.InteropServices.DllImport("SDL2", EntryPoint="SDL_GetError")] public static extern System.IntPtr GetError(); [System.Runtime.InteropServices.DllImport("SDL2", EntryPoint="SDL_Quit")] public static extern void Quit(); } }
今回実際に使ったのはGetError()だけでした。
SDL2_mixer
namespace GIHPLib { using Mix = SDL2_mixer; using Mix_Music = System.IntPtr; public class SDL2_mixer { public enum MIX_InitFlags { MIX_INIT_FLAC = 0x00000001, MIX_INIT_MOD = 0x00000002, MIX_INIT_MP3 = 0x00000008, MIX_INIT_OGG = 0x00000010, MIX_INIT_MID = 0x00000020 } public const int MIX_MAX_VOLUME = 128 ; public delegate void PlayingFinishedHandler(); /* mixer初期化 */ [System.Runtime.InteropServices.DllImport("SDL2_mixer", EntryPoint="Mix_Init")] public static extern int Init(int flags); [System.Runtime.InteropServices.DllImport("SDL2_mixer", EntryPoint="Mix_Quit")] public static extern void Quit(); /* audio初期化 */ [System.Runtime.InteropServices.DllImport("SDL2_mixer", EntryPoint="Mix_OpenAudio")] public static extern int OpenAudio(int frequency, System.UInt16 format, int channels, int chunksize); [System.Runtime.InteropServices.DllImport("SDL2_mixer", EntryPoint="Mix_CloseAudio")] public static extern void CloseAudio(); /* 確保、開放 */ [System.Runtime.InteropServices.DllImport("SDL2_mixer", EntryPoint="Mix_LoadMUS")] public static extern Mix_Music LoadMUS(string file_name); [System.Runtime.InteropServices.DllImport("SDL2_mixer", EntryPoint="Mix_FreeMusic")] public static extern void FreeMusic(Mix_Music music); /* 設定 */ [System.Runtime.InteropServices.DllImport("SDL2_mixer", EntryPoint="Mix_HookMusicFinished")] public static extern void HookMusicFinished(PlayingFinishedHandler handler); [System.Runtime.InteropServices.DllImport("SDL2_mixer", EntryPoint="Mix_VolumeMusic")] public static extern int VolumeMusic(int volume); /* 操作 */ [System.Runtime.InteropServices.DllImport("SDL2_mixer", EntryPoint="Mix_PlayMusic")] public static extern int PlayMusic(Mix_Music music, int loop); [System.Runtime.InteropServices.DllImport("SDL2_mixer", EntryPoint="Mix_HaltMusic")] public static extern int HaltMusic(); /* 状態取得 */ [System.Runtime.InteropServices.DllImport("SDL2_mixer", EntryPoint="Mix_PlayingMusic")] public static extern int PlayingMusic(); } }
こちらも、全部は使ってないです。
オーディオファイルを演奏する手順
大雑把な流れです。
GIHPLib.SDL2.Init(GIHPLib.SDL2.SDL_INIT_AUDIO);/* 何故か呼ばなくても動きます。実際、今回のアプリでは呼んでません。 */ GIHPLib.SDL2_mixer.OpenAudio(44100, (ushort)GIHPLib.SDL2.AUDIO_S16SYS, 2, 4096); music = GIHPLib.SDL2_mixer.LoadMUS(filepath);/* flacやmp3等などが開けます。 */ GIHPLib.SDL2_mixer.PlayMusic(music, loopcount);/* 再生を始めます。再生は別スレッドで行われるので、この呼び出しはすぐに帰ってきます。 */ GIHPLib.SDL2_mixer.HaltMusic();/* 再生を停止します。これを呼ばなくてもFreeMusicの呼び出しで停止します。 */ GIHPLib.SDL2_mixer.FreeMusic(music);/* メモリ等のリソースを開放します。 */ GIHPLib.SDL2_mixer.CloseAudio(); GIHPLib.SDL2.Quit();/* Initが要らなかったので、これもいらないです。 */
Mix_OpenAudioのドキュメントによると、先にSDL_Init(SDL_INIT_AUDIO)を呼ばなければならないのですが、これを呼ばずにいきなりSDL2_mixer.OpenAudio(...)を呼んでも問題なかったので、このアプリではSDL2.InitとSDL2.Quitは省略しています。
SDL2_mixerでアラームを鳴らせるようキッチンタイマーを改造
SDL2_mixerの初期化と後処理
まず次のフィールドを追加します。
//クラスMainWindowのフィールド IntPtr music; bool counting/*これは前回からありました*/, sdl_init_success;
コンストラクタでSDL2_mixer.OpenAudioを呼び出します。
//MainWindowのコンストラクタ sdl_init_success = false; music = IntPtr.Zero; if(GIHPLib.SDL2_mixer.OpenAudio(44100, (ushort)GIHPLib.SDL2.AUDIO_S16SYS, 2, 4096) != 0) { var md = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, "SDL_mixerの初期化に失敗しました。\nSDLからの報告内容:" + System.Runtime.InteropServices.Marshal.PtrToStringAnsi(GIHPLib.SDL2.GetError())); md.Run(); md.Destroy(); } else{ sdl_init_success = true; }
メソッドWindow_DeleteEventでSDL2_mixer.CloseAudioを呼び出します。
//メソッドMainWindow.Window_DeleteEvent内 if(sdl_init_success) { if(music != IntPtr.Zero) { GIHPLib.SDL2_mixer.HaltMusic(); GIHPLib.SDL2_mixer.FreeMusic(music); } GIHPLib.SDL2_mixer.CloseAudio(); }
アラーム関連の操作ができるようボタンを追加
GladeでGUIを再設計し、こんな感じにしました。
ID割当は、
- 表示用ラベル:time_display(前回から変更)
- 分+ボタン:m_up_buton
- 分-ボタン:m_down_buton
- 秒+ボタン:s_up_buton
- 秒-ボタン:s_down_buton
- アラーム停止ボタン:alarm_stop_button
- アラーム選択ボタン:alarm_choose_button
このようになりました。
前回、表示用ラベルのID _label1はプロジェクト作成時に生成されたものそのままでした。アンダーバーから始まっていて統一感が無く気持ち悪かったので今回変更しました。
追加したボタンにイベント設定
アラーム停止ボタンとアラーム選択ボタンにイベントハンドラを登録します。
まずはフィールド宣言です。前回のように[UI]属性を付け、IDと同じ名前にします。
//クラスMainWindowのフィールド [UI] private Button alarm_stop_button = null; [UI] private Button alarm_choose_button = null;
次にイベントハンドラを登録します。
//MainWindowのコンストラクタ
alarm_stop_button.ButtonReleaseEvent += stopAlarm;
alarm_choose_button.ButtonReleaseEvent += chooseAlarm;
イベントハンドラの本体を書きます。
//MainWindowメンバメソッド void stopAlarm(object sender, EventArgs e) { try { GIHPLib.SDL2_mixer.HaltMusic(); } catch(System.Exception err) { System.Console.Error.WriteLine(err.Message); } } void chooseAlarm(object sender, EventArgs e) { if(sdl_init_success) { FileChooserDialog fc = new FileChooserDialog( "報知音に使用する音楽ファイルの選択", this, FileChooserAction.Open, "取り消し", ResponseType.Cancel, "開く", ResponseType.Accept ); FileFilter ff; ff = new FileFilter(); ff.AddMimeType("audio/flac"); ff.AddPattern("*.flac"); ff.Name = "flacオーディオ"; fc.AddFilter(ff); ff = new FileFilter(); ff.AddMimeType("audio/wav"); ff.AddPattern("*.wav"); ff.Name = "wavオーディオ"; fc.AddFilter(ff); ff = new FileFilter(); ff.AddMimeType("audio/x_mpg"); ff.AddMimeType("audio/x_mp3"); ff.AddPattern("*.mp3"); ff.Name = "MPEG1 Audio Layer3"; fc.AddFilter(ff); ff = new FileFilter(); ff.AddMimeType("audio/ogg"); ff.AddPattern("*.ogg"); ff.AddPattern("*.oga"); ff.Name = "Ogg Vorbis"; fc.AddFilter(ff); if(fc.Run() == (int)ResponseType.Accept) { if(music != IntPtr.Zero) { GIHPLib.SDL2_mixer.HaltMusic(); GIHPLib.SDL2_mixer.FreeMusic(music); } music = GIHPLib.SDL2_mixer.LoadMUS(fc.Filename); if(music == IntPtr.Zero) { MessageDialog md = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, "SDL_mixerでファイルを開くことに失敗しました。\nSDLからの報告内容:" + System.Runtime.InteropServices.Marshal.PtrToStringAnsi(GIHPLib.SDL2.GetError()) + "\nなお、Windows版SDL_mixerはASCIIのみのファイル名しか扱えないようです。\nまたLinux版SDL_mixerはディストリビューションによってはmp3は再生できないようです。"); md.Run(); md.Destroy(); } } fc.Destroy(); } else { MessageDialog md = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, "SDL初期化に失敗しています。報知音を鳴らせません。"); md.Run(); md.Destroy(); } }
タイマースレッドからアラーム再生イベントを受け付ける
TimerMainクラスにデリゲートとそのイベントフィールドを追加します。
//クラスTimerMainのフィールド public delegate void Alarmer(); public event Alarmer alarm;
カウントダウンで残り0秒になった瞬間イベントを発生させます。
//メソッドTimerMain.startの0秒以下条件分岐部 if(left_sec <= 0) { pause = true; if(alarm != null) { alarm(); } }
MainWindow側にイベントハンドラを用意します。
//MainWindowのコンストラクタ
tm.alarm += alarm;
//MainWindowメンバメソッド void alarm() { Application.Invoke(delegate { if(sdl_init_success) { if(music != IntPtr.Zero) { if(GIHPLib.SDL2_mixer.PlayMusic(music, -1) == -1) { MessageDialog md = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, "SDL_mixerでの報知音の再生に失敗しました。\nSDLからの報告内容:" + System.Runtime.InteropServices.Marshal.PtrToStringAnsi(GIHPLib.SDL2.GetError())); md.Run(); md.Destroy(); } } } else { MessageDialog md = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, "SDL初期化に失敗しています。報知音を鳴らせません。"); md.Run(); md.Destroy(); } }); }
これで完成
これでとりあえず何かしらをアラームとして鳴らせるようになりました。
Choose alarmボタンを押すとファイル選択ダイアログが表示されアラームとしてflacやoga、mp3などが開けます。以降の0秒カウント時に鳴らされます。
MainWindow.csの全文も載せます。
using System; using Gtk; using UI = Gtk.Builder.ObjectAttribute; namespace KitchenTimer { class MainWindow: Window { [UI] private Label time_display = null; [UI] private Button m_up_button = null; [UI] private Button m_down_button = null; [UI] private Button s_up_button = null; [UI] private Button s_down_button = null; [UI] private Button start_stop_button = null; [UI] private Button alarm_stop_button = null; [UI] private Button alarm_choose_button = null; TimerMain tm; IntPtr music; bool counting, sdl_init_success; public MainWindow() : this(new Builder("MainWindow.glade")) { } private MainWindow(Builder builder) : base(builder.GetObject("MainWindow").Handle) { builder.Autoconnect(this); tm = new TimerMain(); tm.updateLeftTime += updateLabelText; tm.setStat += setStat; tm.alarm += alarm; start_stop_button.ButtonReleaseEvent += startTimer; DeleteEvent += Window_DeleteEvent; counting = false; m_up_button.ButtonReleaseEvent += incM; m_down_button.ButtonReleaseEvent += decM; s_up_button.ButtonReleaseEvent += incS; s_down_button.ButtonReleaseEvent += decS; alarm_stop_button.ButtonReleaseEvent += stopAlarm; alarm_choose_button.ButtonReleaseEvent += chooseAlarm; updateLabelText(); sdl_init_success = false; music = IntPtr.Zero; if(GIHPLib.SDL2_mixer.OpenAudio(44100, (ushort)GIHPLib.SDL2.AUDIO_S16SYS, 2, 4096) != 0) { var md = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, "SDL_mixerの初期化に失敗しました。\nSDLからの報告内容:" + System.Runtime.InteropServices.Marshal.PtrToStringAnsi(GIHPLib.SDL2.GetError())); md.Run(); md.Destroy(); } else{ sdl_init_success = true; } } private void Window_DeleteEvent(object sender, DeleteEventArgs a) { if(sdl_init_success) { if(music != IntPtr.Zero) { GIHPLib.SDL2_mixer.HaltMusic(); GIHPLib.SDL2_mixer.FreeMusic(music); } GIHPLib.SDL2_mixer.CloseAudio(); } tm.exitThread(); Application.Quit(); } private void updateLabelText() { Application.Invoke(delegate { int sec = tm.getLeftTime(), show_min = sec / 60, show_sec = sec - (60 * show_min) ; time_display.Markup = "<span font='32.0' weight='bold'>" + show_min.ToString() + "分\t" + show_sec.ToString() + "秒" + "</span>"; }); } void startTimer(object sender, EventArgs e) { if(counting) { tm.pauseCountdown(); } else { tm.resumeCountdown(); } } void setStat(bool stat) { Application.Invoke(delegate { counting = stat; if(stat) { m_up_button.Sensitive = false; m_down_button.Sensitive = false; s_up_button.Sensitive = false; s_down_button.Sensitive = false; } else { m_up_button.Sensitive = true; m_down_button.Sensitive = true; s_up_button.Sensitive = true; s_down_button.Sensitive = true; } }); } void incM(object sender, EventArgs e) { tm.setLeftTime(tm.getLeftTime() + 60); updateLabelText(); } void decM(object sender, EventArgs e) { int sec = tm.getLeftTime(); sec -= 60; if(sec < 0) { sec += 60; } tm.setLeftTime(sec); updateLabelText(); } void incS(object sender, EventArgs e) { tm.setLeftTime(tm.getLeftTime() + 1); updateLabelText(); } void decS(object sender, EventArgs e) { int sec = tm.getLeftTime(); sec -= 1; if(sec < 0) { sec = 0; } tm.setLeftTime(sec); updateLabelText(); } void alarm() { Application.Invoke(delegate { if(sdl_init_success) { if(music != IntPtr.Zero) { if(GIHPLib.SDL2_mixer.PlayMusic(music, -1) == -1) { MessageDialog md = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, "SDL_mixerでの報知音の再生に失敗しました。\nSDLからの報告内容:" + System.Runtime.InteropServices.Marshal.PtrToStringAnsi(GIHPLib.SDL2.GetError())); md.Run(); md.Destroy(); } } } else { MessageDialog md = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, "SDL初期化に失敗しています。報知音を鳴らせません。"); md.Run(); md.Destroy(); } }); } void stopAlarm(object sender, EventArgs e) { try { GIHPLib.SDL2_mixer.HaltMusic(); } catch(System.Exception err) { System.Console.Error.WriteLine(err.Message); } } void chooseAlarm(object sender, EventArgs e) { if(sdl_init_success) { FileChooserDialog fc = new FileChooserDialog( "報知音に使用する音楽ファイルの選択", this, FileChooserAction.Open, "取り消し", ResponseType.Cancel, "開く", ResponseType.Accept ); FileFilter ff; ff = new FileFilter(); ff.AddMimeType("audio/flac"); ff.AddPattern("*.flac"); ff.Name = "flacオーディオ"; fc.AddFilter(ff); ff = new FileFilter(); ff.AddMimeType("audio/wav"); ff.AddPattern("*.wav"); ff.Name = "wavオーディオ"; fc.AddFilter(ff); ff = new FileFilter(); ff.AddMimeType("audio/x_mpg"); ff.AddMimeType("audio/x_mp3"); ff.AddPattern("*.mp3"); ff.Name = "MPEG1 Audio Layer3"; fc.AddFilter(ff); ff = new FileFilter(); ff.AddMimeType("audio/ogg"); ff.AddPattern("*.ogg"); ff.AddPattern("*.oga"); ff.Name = "Ogg Vorbis"; fc.AddFilter(ff); if(fc.Run() == (int)ResponseType.Accept) { if(music != IntPtr.Zero) { GIHPLib.SDL2_mixer.HaltMusic(); GIHPLib.SDL2_mixer.FreeMusic(music); } music = GIHPLib.SDL2_mixer.LoadMUS(fc.Filename); if(music == IntPtr.Zero) { MessageDialog md = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, "SDL_mixerでファイルを開くことに失敗しました。\nSDLからの報告内容:" + System.Runtime.InteropServices.Marshal.PtrToStringAnsi(GIHPLib.SDL2.GetError()) + "\nなお、Windows版SDL_mixerはASCIIのみのファイル名しか扱えないようです。\nまたLinux版SDL_mixerはディストリビューションによってはmp3は再生できないようです。"); md.Run(); md.Destroy(); } } fc.Destroy(); } else { MessageDialog md = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, "SDL初期化に失敗しています。報知音を鳴らせません。"); md.Run(); md.Destroy(); } } } class TimerMain { bool exit, pause, sleeping; int left_sec; System.Threading.Thread th; System.Threading.SemaphoreSlim running_lock, pausing_lock, controll_lock; public delegate void LeftTimeUpdater(); public event LeftTimeUpdater updateLeftTime; public delegate void CountingStatListener(bool stat); public event CountingStatListener setStat; public delegate void Alarmer(); public event Alarmer alarm; void start(Object obj) { running_lock.Wait(); sleeping = false; while(!exit) { if(pause) { pausing_lock.Wait(); if(setStat != null) { setStat(false); } running_lock.Release(); try { sleeping = true; System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite); } catch(System.Threading.ThreadInterruptedException e) { sleeping = false; } running_lock.Wait(); if(setStat != null) { setStat(true); } pausing_lock.Release(); } if(updateLeftTime != null) { updateLeftTime(); } if(left_sec <= 0) { pause = true; if(alarm != null) { alarm(); } } else { try { System.Threading.Thread.Sleep(999); } catch(System.Exception e) { } left_sec--; } } } public TimerMain() { exit = false; pause = true; th = new System.Threading.Thread(start); running_lock = new System.Threading.SemaphoreSlim(1, 1); pausing_lock = new System.Threading.SemaphoreSlim(1, 1); controll_lock = new System.Threading.SemaphoreSlim(1, 1); th.Start(); } public bool setLeftTime(int sec) { using(var l1 = new GIHPLib.Locker(controll_lock)) { using(var l2 = new GIHPLib.Locker(running_lock, try_lock: true)) { if(l2.hasLock()) { left_sec = sec; return true; } return false; } } } public int getLeftTime() { return left_sec; } public bool resumeCountdown() { using(var l1 = new GIHPLib.Locker(controll_lock)) { using(var l2 = new GIHPLib.Locker(running_lock, try_lock: true)) { if(l2.hasLock()) { if(pause) { pause = false; th.Interrupt(); } return true; } return false; } } } public bool pauseCountdown() { using(var l1 = new GIHPLib.Locker(controll_lock)) { using(var l2 = new GIHPLib.Locker(pausing_lock, try_lock: true)) { if(l2.hasLock()) { pause = true; return true; } return false; } } } public void exitThread() { using(var l = new GIHPLib.Locker(controll_lock)) { exit = true; System.Threading.Thread.Yield(); while(!th.Join(0)) { if(sleeping) { th.Interrupt(); } System.Threading.Thread.Yield(); } } } } }
問題点
まず、Windowsで日本語を含むパスを開けないという致命傷を負っています。
また、LinuxではSDL2_mixerのディストリビューションの公式配布パッケージを使う場合、コンパイル時の設定などによってmp3デコードが不可能になっている場合があります。少なくともFedoraはそうでした。
Windowsの日本語の件に関しては、対処方法を探ってみたいと思います。
あと、アラームを設定として記憶できるようにするべきだとも思います。
.NET CoreとGtkSharpでGUIアプリを作ってみる[GUI作成]
.NET Coreですが、せっかくクロスプラットフォームなのにGUIが無い…orz
…というわけでGtkSharpを使って.NET CoreでGUIアプリを作ってみます。
なお、.NET Coreでは、C#とF#、VBが扱えますが、本記事ではC#を使います。
GtkSharpって?
C言語用のGUIツールキットGTK+をC#から使えるようにしたC#ライブラリです。Monoという、クロスプラットフォームな.NET Framework互換環境でGUIツールキットとして利用されてきました。今回使うのは.NET Coreへ対応させたフォークプロジェクト(
NuGetのページ)のものであり、.NET Standard 2.0 を満たす.NET実装から利用できるようで、.NET Core >= 2.0 から利用できるということになります。
Linuxでは一部のGnomeアプリなど(BansheeやGnome-RDPなど)に使われているかと思います。
ただの関数呼び出しの橋渡しではなく、C#らしく使えるようになっており(イベントハンドラ周りや属性を利用したGUIウィジェットの自動割当など)、GTK+をバックエンドとした別物のGUIツールキットと言えるかと思います。
.NET CoreでGtkSharpを使えるようにする
GtkSharpのプロジェクトページに導入方法が記述されています。基本的にはこれに従いますが、Gladeのインストールもついでにしました。(なお、GladeとはGTK+やその各種バインディングで用いられるGUI構築ツールのことです。)
GTK+3とGladeのインストール
最近のFedoraの場合
sudo dnf install gtk3-devel glade
Windowsの場合
MSYS2を利用します。MSYS2は導入済みで、Pathも通してあるものとします。またここでは64bit版を使用します。
pacman -S mingw-w64-x86_64-gtk3 mingw-w64-x86_64-glade
GtkSharpアプリプロジェクトの雛形を追加
dotnet new --install GtkSharp.Template.CSharp
雛形gtkappが使用可能になりました。これにより、以降
dotnet new gtkapp
と、コマンドを打つだけでGtkSharpを使った.NET Coreプロジェクトを作成できます。
また、GtkSharp.Template.FSharp、GtkSharp.Template.VBNetを入れることで、F#、VBプロジェクト用の雛形も利用できるようです。
試しにキッチンタイマーでも作ってみる
新規プロジェクト作成
GtkSharpを使った新規.NET Coreプロジェクトを作成します。上記でインストールした雛形gtkappを使います
名前はKitchenTimerとしました。
mkdir KitchenTimer cd KitchenTimer dotnet new gtkapp
プロジェクト作成直後の生成物
gtkappプロジェクトを作成すると、4つのファイルが作成されます。次に示すのはls -lの出力結果です。
[geeksinhitachiprovince on KitchenTimer]$ll 合計 16 -rw-rw-r--. 1 geeksinhitachiprovince geeksinhitachiprovince 455 1月 20 02:58 KitchenTimer.csproj -rw-rw-r--. 1 geeksinhitachiprovince geeksinhitachiprovince 910 1月 20 02:58 MainWindow.cs -rw-rw-r--. 1 geeksinhitachiprovince geeksinhitachiprovince 1844 1月 20 02:58 MainWindow.glade -rw-rw-r--. 1 geeksinhitachiprovince geeksinhitachiprovince 484 1月 20 02:58 Program.cs
なお、本記事ではProgram.csは一切いじりません。
ビルドして実行すると
こんな感じのHelloWorldアプリになっています。色が黒っぽいのはFedora26のcinnamonのデフォルトテーマによるものです。
ボタンをクリックすると、
「Hello World!」のテキストの後にクリック回数が追記されます。
まずはMainWindow.gladeを編集し、GUIを設計
GUI設計はGladeというツールで行います。GtkSharpではGUI部品はGtk.Widgetの派生クラスとなっており、Gladeで設計したGUIの各部品は、コード上でGtk.Widget派生クラスオブジェクトとして扱えます。gladeファイルはこのツールで生成されるGUI設計を記述したxmlファイルです。
では、GladeでMainWindow.gladeを開いて編集してみます。
最初は、HelloWorldアプリのGUI設計になっています。
なお、最新のGladeの見た目が変わっており、最近のGnomeアプリのようにメニューバーを廃止してタイトルバーに各項目を配置しています。下のスクリーンショットはMSYS2で入れたWindows版のものです。
キッチンタイマーに必要なウィジェットをつけていき、GUI設計をします。ここでは、分、秒の+/-ボタン、スタート/ストップボタン、表示用のラベルを取り付けました。
Gladeのウィンドウのプロパティ設定部(の全般タブ)にIDという項目があります。このIDがC#ソースコード内でのフィールド名になるので、ちゃんと意味のある名前にします。
ここでは、IDを以下のようにしました。
- 表示用ラベル:_label1(HelloWorldアプリのものをそのまま流用)
- 分+ボタン:m_up_buton
- 分-ボタン:m_down_buton
- 秒+ボタン:s_up_buton
- 秒-ボタン:s_down_buton
gladeファイルのC#コード上からの利用
MainWindow.csは最初こんな感じになっていると思います。
using System; using Gtk; using UI = Gtk.Builder.ObjectAttribute; namespace KitchenTimer { class MainWindow : Window { [UI] private Label _label1 = null; [UI] private Button _button1 = null; private int _counter; public MainWindow() : this(new Builder("MainWindow.glade")) { } private MainWindow(Builder builder) : base(builder.GetObject("MainWindow").Handle) { builder.Autoconnect(this); DeleteEvent += Window_DeleteEvent; _button1.Clicked += Button1_Clicked; } private void Window_DeleteEvent(object sender, DeleteEventArgs a) { Application.Quit(); } private void Button1_Clicked(object sender, EventArgs a) { _counter++; _label1.Text = "Hello World! This button has been clicked " + _counter + " time(s)."; } } }
ソースコードを見ると、MainWindowのコンストラクタ内でgladeファイルを使ってGtk.Builderオブジェクトを構築しています。そしてGtk.BuiderオブジェクトのAutoconnect()メソッドを呼び出しています。
これだけでGUIの構築は済んでしまいます。
後は構築されたGUIウィジェットオブジェクトをフィールドに代入してもらう方法ですが、ここでフィールド宣言部を見てみます。
[UI] private Label _label1 = null; [UI] private Button _button1 = null;
[UI]属性(using UI = Gtk.Builder.ObjectAttribute;)の付いたフィールドが宣言されていて、そのフィールド名がGladeで設定されているIDと一致していることが確認できると思いまが、Autoconnect()メソッドは、この属性とフィールド名を頼りにしているようです。
というわけで、次の形式でフィールドを宣言します。
[UI] private/public 型(Gtk.Widget派生クラス) フィールド名(Gladeで設定したID) = null
このキッチンタイマーアプリでは、ウィジェット用フィールド宣言部は次のようになります。
/*省略*/ class MainWindow: Window { [UI] private Label _label1 = null; [UI] private Button m_up_button = null; [UI] private Button m_down_button = null; [UI] private Button s_up_button = null; [UI] private Button s_down_button = null; [UI] private Button start_stop_button = null; /*省略*/ } /*省略*/
Gladeの画面(右)とソースコード(左)の関係を図示すると
こんな感じです。
ボタン操作への反応
Gtk.ButtonにはSystem.EventHandlerデリゲートのイベントプロパティがいくつかあり、そのうちボタン押下への反応に使えるのは、
- Clicked
- ButtonPressEvent
- ButtonReleaseEvent
この3つでしょう。この内の一つにSystem.EventHandlerデリゲートとして使用可能なメソッドを+=で追加するだけです。
/*省略*/ namespace KitchenTimer { class MainWindow: Window { [UI] private Label _label1 = null; [UI] private Button m_up_button = null; [UI] private Button m_down_button = null; [UI] private Button s_up_button = null; [UI] private Button s_down_button = null; [UI] private Button start_stop_button = null; /*省略*/ public MainWindow() : this(new Builder("MainWindow.glade")) { } private MainWindow(Builder builder) : base(builder.GetObject("MainWindow").Handle) { builder.Autoconnect(this); /*省略*/ start_stop_button.ButtonReleaseEvent += startTimer; /*省略*/ m_up_button.ButtonReleaseEvent += incM; m_down_button.ButtonReleaseEvent += decM; s_up_button.ButtonReleaseEvent += incS; s_down_button.ButtonReleaseEvent += decS; /*省略*/ } /*省略*/ void startTimer(object sender, EventArgs e) { /*ボタン操作への反応*/ } /*省略*/ void incM(object sender, EventArgs e) { /*ボタン操作への反応*/ } void decM(object sender, EventArgs e) { /*ボタン操作への反応*/ } void incS(object sender, EventArgs e) { /*ボタン操作への反応*/ } void decS(object sender, EventArgs e) { /*ボタン操作への反応*/ } } }
終了時の後処理
再度生成されたコードを見てみると、
DeleteEvent += Window_DeleteEvent;
という行があります。DeleteEventはGtk.Widgetのイベントフィールドで、ウィンドウが閉じられたときに呼び出されます。よって終了時の後処理をするための雛形はもうできていることになります。追加の後処理がある場合、メソッドWindow_DeleteEventに追加するといいでしょう。
private void Window_DeleteEvent(object sender, DeleteEventArgs a) { /*追加の後処理*/ Application.Quit();/*この呼び出しがないと終了しない*/ }
GUIオブジェクトをGUIスレッドの外から操作
このキッチンタイマーアプリはGUIスレッドとタイマースレッドに分けて作成しますが、タイマースレッドが一秒刻むごとに表示用ラベル_label1の表示を書き換える必要があります。GtkSharpのプロパティ操作はスレッドセーフではないので、_label1の表示を巡ってタイマースレッドとGUIスレッドが競合します。これを避けるためにタイマースレッドからGUIスレッドに操作を依頼する必要があります。これをしてくれるのが、Gtk.Application.Invoke(System.EventHandler)です。
Application.Invoke ( delegate{ /*GUIスレッドで実行する操作*/ } );
だいたいこんな感じの使い方になると思います(名前空間Gtkはusingされているので省略しています)。
最終的にこうなった
以上のことを踏まえて、外部スレッドで動くタイマーなどを作成し、出来上がったのがこちらです。
using System; using Gtk; using UI = Gtk.Builder.ObjectAttribute; namespace KitchenTimer { class MainWindow: Window { [UI] private Label _label1 = null; [UI] private Button m_up_button = null; [UI] private Button m_down_button = null; [UI] private Button s_up_button = null; [UI] private Button s_down_button = null; [UI] private Button start_stop_button = null; TimerMain tm; bool counting; public MainWindow() : this(new Builder("MainWindow.glade")) { } private MainWindow(Builder builder) : base(builder.GetObject("MainWindow").Handle) { builder.Autoconnect(this); tm = new TimerMain(); tm.updateLeftTime += updateLabelText; tm.setStat += setStat; start_stop_button.ButtonReleaseEvent += startTimer; DeleteEvent += Window_DeleteEvent; counting = false; m_up_button.ButtonReleaseEvent += incM; m_down_button.ButtonReleaseEvent += decM; s_up_button.ButtonReleaseEvent += incS; s_down_button.ButtonReleaseEvent += decS; updateLabelText(); } private void Window_DeleteEvent(object sender, DeleteEventArgs a) { tm.exitThread(); Application.Quit(); } private void updateLabelText() { int sec = tm.getLeftTime(), show_min = sec / 60, show_sec = sec - (60 * show_min) ; _label1.Markup = "<span font='32.0' weight='bold'>" + show_min.ToString() + "分\t" + show_sec.ToString() + "秒" + "</span>"; } void startTimer(object sender, EventArgs e) { if(counting) { tm.pauseCountdown(); } else { tm.resumeCountdown(); } } void setStat(bool stat) { counting = stat; if(stat) { m_up_button.Sensitive = false; m_down_button.Sensitive = false; s_up_button.Sensitive = false; s_down_button.Sensitive = false; } else { m_up_button.Sensitive = true; m_down_button.Sensitive = true; s_up_button.Sensitive = true; s_down_button.Sensitive = true; } } void incM(object sender, EventArgs e) { tm.setLeftTime(tm.getLeftTime() + 60); updateLabelText(); } void decM(object sender, EventArgs e) { int sec = tm.getLeftTime(); sec -= 60; if(sec < 0) { sec += 60; } tm.setLeftTime(sec); updateLabelText(); } void incS(object sender, EventArgs e) { tm.setLeftTime(tm.getLeftTime() + 1); updateLabelText(); } void decS(object sender, EventArgs e) { int sec = tm.getLeftTime(); sec -= 1; if(sec < 0) { sec = 0; } tm.setLeftTime(sec); updateLabelText(); } } class TimerMain { bool exit, pause, sleeping; int left_sec; System.Threading.Thread th; System.Threading.SemaphoreSlim running_lock, pausing_lock, controll_lock; public delegate void LeftTimeUpdater(); public event LeftTimeUpdater updateLeftTime; public delegate void CountingStatListener(bool stat); public event CountingStatListener setStat; void start(Object obj) { running_lock.Wait(); sleeping = false; while(!exit) { if(pause) { pausing_lock.Wait(); if(setStat != null) { Application.Invoke(delegate { setStat(false); }); } running_lock.Release(); try { sleeping = true; System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite); } catch(System.Threading.ThreadInterruptedException e) { sleeping = false; } running_lock.Wait(); if(setStat != null) { Application.Invoke(delegate { setStat(true); }); } pausing_lock.Release(); } if(updateLeftTime != null) { Application.Invoke(delegate { updateLeftTime(); }); } if(left_sec <= 0) { pause = true; } else { System.Threading.Thread.Sleep(999); left_sec--; } } } public TimerMain() { exit = false; pause = true; th = new System.Threading.Thread(start); running_lock = new System.Threading.SemaphoreSlim(1, 1); pausing_lock = new System.Threading.SemaphoreSlim(1, 1); controll_lock = new System.Threading.SemaphoreSlim(1, 1); th.Start(); } public bool setLeftTime(int sec) { using(var l1 = new GIHPLib.Locker(controll_lock)) { using(var l2 = new GIHPLib.Locker(running_lock, try_lock: true)) { if(l2.hasLock()) { left_sec = sec; return true; } return false; } } } public int getLeftTime() { return left_sec; } public bool resumeCountdown() { using(var l1 = new GIHPLib.Locker(controll_lock)) { using(var l2 = new GIHPLib.Locker(running_lock, try_lock: true)) { if(l2.hasLock()) { if(pause) { pause = false; th.Interrupt(); } return true; } return false; } } } public bool pauseCountdown() { using(var l1 = new GIHPLib.Locker(controll_lock)) { using(var l2 = new GIHPLib.Locker(pausing_lock, try_lock: true)) { if(l2.hasLock()) { pause = true; return true; } return false; } } } public void exitThread() { using(var l = new GIHPLib.Locker(controll_lock)) { exit = true; System.Threading.Thread.Yield(); while(!th.Join(0)) { if(sleeping) { th.Interrupt(); } System.Threading.Thread.Yield(); } } } } }
GIHPLib.Lockerという見慣れないクラスがあるかと思いますが、これはC++のstd::unique_lockみたいなのが欲しかったので作ったものです。一つのSystem.Threading.SemaphoreSlimオブジェクトを管理対象とし、usingステートメントを抜けると管理対象Wait()済みであればRelease()します。nupkg化してあるので、このアプリのプロジェクトには含まれておらず、KitchenTimer.csprojに参照するパッケージとして記述してあります。
namespace GIHPLib { public class Locker: System.IDisposable { System.Threading.SemaphoreSlim semaphore; bool have_lock; public Locker(System.Threading.SemaphoreSlim init_semaphore, bool with_lock = true, bool try_lock = false) { semaphore = init_semaphore; if(with_lock && semaphore != null) { if(try_lock) { if(semaphore.Wait(0)){ have_lock = true; } else{ have_lock = false; } } else { semaphore.Wait(); have_lock = true; } } else { have_lock = false; } } public void getLock(bool try_lock = false) { if(!have_lock && semaphore != null) { if(try_lock) { if(semaphore.Wait(0)){ have_lock = true; } else{ have_lock = false; } } else { semaphore.Wait(); have_lock = true; } } } public void releaseLock() { if(have_lock && semaphore != null) { semaphore.Release(); have_lock = false; } } public bool hasLock(){ return have_lock; } public void Dispose() { if(have_lock && semaphore != null) { semaphore.Release(); have_lock = false; } } } }
実は、C#の経験は、ASP.NET CoreでRaspberry Piを制御するための簡単なWebページを作ったことがあるという程度で、故に不慣れな感じのコードになっているかもしれません。
普段はC++とQt(GUIツールキット)で店頭イベント用の小さなアプリとか作っているのですが、C++のstd::mutexやstd::unique_lock等の単純で簡単なものが欲しくなりました。
次はSDLでアラーム対応化
このアプリ、キッチンタイマーのくせにアラームがなりません。というわけで、次はSDL2_mixerで何かしら鳴らせるようにしてみます。
ニュートン・ラフソン法による逆数の求め方メモ
そもそもニュートン・ラフソン法ってなんだっけ?
ニュートン・ラフソン法(単にニュートン法ともいう)とは、方程式の解を近似値として反復的に求める手法です。
ニュートン・ラフソン法で逆数や平方根などを求める場合、普通はの解がその求めたい数になるようにします。
ということで最終目標は、を満たすときのを求めることです。
大雑把な流れを書いていきます。
まずとして有効な値を決め、初期値とします。
左図の赤線をこのとします。
次にで接するの接線を考えます。
左図の青い線をこの接線とします。
この接線と軸との交点における座標をとします。
今度は、で接するの接線を考えます。
先ほどと同じように、接線と軸との交点における座標を、今度はとします。
以下、同様に
と、を求めていきます。
すると、やがてはと軸との交点に近づいていきます。
左図でとを比較すると、解であると軸との交点に近づいていることがわかります。
これは、がの解に収束することを意味します。
以上のようにして、ニュートン・ラフソン法での解を近似値として求めることができます。
逆数を求める
ニュートン・ラフソン法で逆数を求める際の式、
逆数を求めたい数を、Sourceよりとすることとします。を満たすときのの値が解となるようにするので、
(は逆数を求めたい数)
となります。
におけるの接線
まず接線の傾きは、
となります。
接線のy切片の座標をとすると、における接線を表す式(tangent lineよりとします)は、
(はにおける接線)
(は接線のy切片の座標)
となります。
を求めます。接点座標は、より、となります。これをに当てはめると、
(は接線のy切片の座標)
(は逆数を求めたい数)
と、が求まりました。
以上より、最終的ににおける接線は、
(はにおける接線)
(は逆数を求めたい数)
となります。
からを求める漸化式
からを求めるには、における接線のx切片の座標を求めます。
要するに、先程求めた接線がになる解を求めればいいのです。
これが漸化式となります。
あとは、この漸化式を使ってからへの変化が極僅かとなるまで収束させ続けます。
ニュートン・ラフソン法で逆数を求めるプログラムを書いてみる
Golang(Go言語、正式には単に「Go」らしいです)の入門も兼ねてGoで書いてみました。
A Tour of Go(日本語版)とPackages - The Go Programming Languageに入門者向けの読み物と、パッケージ(API?)一覧がありますので、これを読みながら書きました。
をどうするか
ニュートン・ラフソン法では、が解に近いほど早く収束します。故にはなるべく解に近づけます。
逆数を求める場合は、(逆数を求めたい数)がのときが解より大きすぎると、となってしまい、収束しないはずです。故にを満たすようにします。(のときは不等号を逆にして考えてください。)
最善策がよくわからないので、まずとし、が未満になるまでにをかけ続けるようにしました。
終了条件はどうするか
がに近づき続ける間は、続けるようにしました。
ニュートン・ラフソン法で逆数を求める関数
func reciprocal(Src float64) float64 { if Src == 0.0 { return math.NaN() } var newdist /*終了判定用変数*/, newX /*Xn+1*/ float64 var olddist /*終了判定用変数*/, oldX /*Xn*/ float64 = math.Inf(0), Src * 0.1 //X0を決定。Src*X0が1より小さくなるまでX0を0.1倍し続ける。 for { if Src*oldX <= 1.0 { break } oldX *= 0.1 } for { //漸化式でXn+1を求める newX = oldX * (2.0 - oldX*Src) //前回よりもSrc*Xn+1が1に近づいたら続行 newdist = math.Abs(1.0 - newX*Src) if newdist >= olddist { break } olddist = newdist //Xnを更新 oldX = newX } return oldX }
ニュートン・ラフソン法で逆数を求め除算をするプログラム
上記関数を使って除算をするプログラムです。
package main import ( "fmt" "math" "os" "strconv" ) func main() { var v1, v2 float64 var stat bool fmt.Printf("入力1>> ") stat, v1 = readValidValue() if !stat { fmt.Printf("数値をいれよ\n") return } fmt.Printf("入力は%Fであるな\n", v1) fmt.Printf("入力2>> ") stat, v2 = readValidValue() if !stat { fmt.Printf("数値をいれよ\n") return } fmt.Printf("入力は%Fであるな\n", v2) fmt.Printf("%F/%Fは%Fである\n", v1, v2, divide(v1, v2)) } //数値入力(ふざけた入力は受け付けない) func readValidValue() (bool, float64) { var buf /*直接入力するバッファ*/, num /*入力を溜め込み、後に[]byte→string→float64と変換*/ []byte var dotAppeared, invalid = false, false var ret float64 buf = make([]byte, 1) num = make([]byte, 0, 127) for { //一文字(1byte)入力 os.Stdin.Read(buf) if buf[0] == byte('\n') { //改行コードなら入力終了 if len(num) == 0 { //このとき、入力が改行コードのみだった場合、不正な入力とする invalid = true } break } else if buf[0] == byte('-') && len(num) == 0 { //負の数を入力するため、先頭のみ「-」を許容 num = append(num, buf[0]) } else if buf[0] == byte('-') && len(num) != 0 { //先頭以外で-を入力したら不正な入力とする invalid = true readForNewLine() break } else if buf[0] == byte('.') && len(num) >= 1 && !dotAppeared { //少数入力のため、2文字目以降の初めての「.」のみ許容する dotAppeared = true num = append(num, buf[0]) } else if buf[0] == byte('.') && (len(num) == 0 || dotAppeared) { //先頭または二度目の.なら不正な入力とする invalid = true readForNewLine() break } else if buf[0] >= byte('0') && buf[0] <= byte('9') { //0〜9は常に許容 num = append(num, buf[0]) } else { //あとはすべて不正な入力とする invalid = true readForNewLine() break } } ret, _ = strconv.ParseFloat(string(num), 64) return !invalid, ret } //読み捨て用の関数 func readForNewLine() { buf := make([]byte, 1) for { os.Stdin.Read(buf) if buf[0] == byte('\n') { break } } } //除算を逆数との掛け算として求める関数 func divide(a, b float64) float64 { if b == 0.0 { return math.NaN() } return a * reciprocal(b) } //ニュートン・ラフソン法で逆数を求める関数 func reciprocal(Src float64) float64 { if Src == 0.0 { return math.NaN() } var newdist /*終了判定用変数*/, newX /*Xn+1*/ float64 var olddist /*終了判定用変数*/, oldX /*Xn*/ float64 = math.Inf(0), Src * 0.1 //X0を決定。Src*X0が1より小さくなるまでX0を0.1倍し続ける。 for { if Src*oldX < 1.0 { break } oldX *= 0.1 } for { //漸化式でXn+1を求める newX = oldX * (2.0 - oldX*Src) //前回よりもSrc*Xn+1が1に近づいたら続行 newdist = math.Abs(1.0 - newX*Src) if newdist >= olddist { break } olddist = newdist //Xnを更新 oldX = newX } return oldX }
実行結果
[アホのPC]$go run main.go 入力1>> 8 入力は8.000000であるな 入力2>> 32 入力は32.000000であるな 8.000000/32.000000は0.250000である
無事に計算できてますが、特にGolangでなくてもいいような出来になりました。