単位換算プログラム~PPコンバータ~

#はじめに
皆さんはじめまして。
化学を専門に勉強している学生がプログラミングに手を出してみました。

今回作成したプログラムは科学分野で広く"濃度"の単位として用いられている"Parts Par表記の相互換算を行うプログラムです。

#Parts Par 表記とは
Parts Par表記は厳密には"無次元数"と呼ばれる単位で、全体に対して目的としている物がどの程度の割合で存在しているかを表しています。

存在している割合が
百万分の一ならparts par million(ppm)
十億分の一ならparts par billion(ppb)
一兆分の一ならparts par trillion(ppt)
千兆分の一ならparts par quadrillion(ppq)
になります。

#作成した背景
同級生に単位換算が苦手な学生がおり、検算を頼まれた時に「これ、プログラムにしてしまえば手っ取り早いのでは？」と思ったため。

ただし、パソコンが近くにない場合やパソコンが起動していない場合だと余計な手間がかかってしまうのが問題ですね。

#今回の作成環境
Microsoft Visual Studio
VisualBasic Windows フォームアプリケーション(.NET Framework)

#フォームデザインと使い方
##フォームデザイン
<img width="271" alt="20180510_183851.png" src="https://qiita-image-store.s3.amazonaws.com/0/226419/476478c7-ad4c-7734-19c8-61d759207d78.png">

##使い方
1.いずれかのテキストボックスに数値を入力
2.コンボボックスから変換元の濃度を指定
3.コンバートボタンで変換を実行
4.他のテキストボックスへ結果を出力

#ソースコードの解説

##各種定義部
<img width="729" alt="20180510_183735.png" src="https://qiita-image-store.s3.amazonaws.com/0/226419/c0e69b40-531a-67a0-5766-8f9119ac25ee.png">


・具体的な値を処理する変数では、桁数の大きい小数を取り扱うのでDouble(倍精度浮動小数型)で定義しました。
・String(文字列型)で濃度の名前(4つ分)とエラーメッセージを取り扱う変数をそれぞれ定義しました。
・変換元のデータの判定結果を格納する"ric"とエラー時に利用するための"conti"を定義します。これらに入る数値は整数なのでInteger(整数型)で定義。
(以降まとめて話す場合には"管理用変数"と表記します。)
・メッセージボックスの書式を決定するためのStyle_crをMsgBoxStyleで定義しました。

##初期化操作用のSubプロシージャ
<img width="728" alt="20180510_185830.png" src="https://qiita-image-store.s3.amazonaws.com/0/226419/f141649e-bbef-bb54-c415-00fa57d0f1e9.png">

Text All ClearなのでTACL()という名前になっています。

##Form1ロード時の処理
<img width="724" alt="20180510_184526.png" src="https://qiita-image-store.s3.amazonaws.com/0/226419/41ccd933-8514-47bc-ce3b-5ae91f77d255.png">

初期化用のSubプロシージャをTACL()で呼び出して、各テキストボックスを空欄にして、管理用変数を0にします。

次に、濃度の名前を配列"concentration"にそれぞれ入力します。

配列に入れた濃度名をFor文を使ってコンボボックスに登録していきます。
この方法は複数のコンボボックスに同じ内容を登録する際に重宝します。

メッセージボックスの書式設定では次の内容を規定しています。
・クリティカル(赤い×印のアイコンのメッセージボックス)
・アプリケーションモーダル(ユーザーがメッセージボックスに何らかのリアクションをしない限り、前の画面に戻らせないようにする)
・メッセージボックスセットフォアグラウンド(表示するメッセージボックスを最前面に表示せよ)
・システムモーダル(ユーザーがメッセージボックスに何らかのリアクションをしない限り、プログラムの処理を再開させないようにする)

##ボタン1(コンバートボタン)での処理
<img width="728" alt="20180510_184310.png" src="https://qiita-image-store.s3.amazonaws.com/0/226419/2731c2c8-d7cb-4a03-802c-f09a7aa677b3.png">

この部分で、コンボボックスの中身をSelect Caseを用いて判断しています。

以下、指定の濃度別に計算処理を行っていきます。

<img width="728" alt="20180510_192428.png" src="https://qiita-image-store.s3.amazonaws.com/0/226419/38dd4e1c-7c93-cf4c-10ee-c0fd884247af.png">

ppmでの計算を例に解説します。

まずはじめにcalにテキストボックス1の文字列をVal(数値)として取り込みます。

次に正規化処理を行い、0~9と小数以外の文字列の有無を判定します。
規定文字列以外が含まれている場合、0以下の数値の場合には
"適切な数値を入力してください。"
"エラー内容:未入力、非数値、0以下の数値の入力"
のメッセージを表示し、contiの値を0にします。

入力された文字列に問題がない場合には、contiを1とし、計算用Subプロシージャに飛びます。

##計算処理用のSubプロシージャ
<img width="727" alt="20180510_190056.png" src="https://qiita-image-store.s3.amazonaws.com/0/226419/034619ae-c032-38ed-6c64-06388c4f8c75.png">

CalculationなのでCA()という名前になっています。

入力された値がconに格納されています。

If文を用いて、conの値が10以上であるか、1以下であるかを判定します。
これは、科学的な計算のルールである有効数字というものを考慮するための処理です。

0以上1以下の数字の部分(係数)と10の累乗の形で表記することで、何桁目までが正確な値かが分かります。
(例:120.3 cmのロープを10 cm刻みの定規で測ると1.2×10^2 cmとなり、1 cm刻みの定規ではかると1.203×10^2 cmとなります)

calc(1)に10の何乗か(指数)を格納します。
conの値が10以上ならば、10で割って、指数を+1します。
conの値が1未満ならば、10を掛けて、指数を-1します。

また、calc(2)に係数を格納します。

##ボタン1(コンバートボタン)での処理(続き)
<img width="728" alt="20180510_192428.png" src="https://qiita-image-store.s3.amazonaws.com/0/226419/4d5c6193-91ea-63d2-2dd1-2aa84eedc9c0.png">

次にそれぞれの濃度になるように指数部分を調整します。
ppm=10^(-6)、ppb=10^(-9)、よってppmからppbへは10^(3)大きな値になります。そのためcalc(1)に3を足します。

それぞれ指数の調整が完了したら、係数と指数を掛け合わせて各テキストボックスへ出力します。

また、contiが0の場合には、テキストボックス1~4に"Error"と出力するように以下のSubプロシージャを呼び出しています。
<img width="728" alt="20180510_195224.png" src="https://qiita-image-store.s3.amazonaws.com/0/226419/4712bdec-020b-e436-fff4-75295d59fa79.png">

##ボタン1(コンバートボタン)での処理(続き)
以下同様に各濃度での処理を行っていきます。
<img width="728" alt="20180510_194537.png" src="https://qiita-image-store.s3.amazonaws.com/0/226419/7769eada-7a8d-5671-0fca-f34844f04324.png">
<img width="727" alt="20180510_194558.png" src="https://qiita-image-store.s3.amazonaws.com/0/226419/6a07babf-377e-2aa9-3c61-839b08889ee6.png">
<img width="729" alt="20180510_194625.png" src="https://qiita-image-store.s3.amazonaws.com/0/226419/8aaa618e-9293-dee2-2bb7-9b9bc95f7ec3.png">

なお、変換元のデータが規定されていない場合には、次のプログラムでエラーメッセージを出力します。

<img width="729" alt="20180510_194818.png" src="https://qiita-image-store.s3.amazonaws.com/0/226419/016746bc-19d3-f461-8c55-a652db702729.png">

#ボタン2(クリアボタン)での処理
<img width="729" alt="20180510_194957.png" src="https://qiita-image-store.s3.amazonaws.com/0/226419/50127702-fb31-d38c-7cbc-60f5220dc735.png">

初期化SubプロシージャをTACL()で呼び出しています。

#ボタン3(終了ボタン)での処理
<img width="728" alt="20180510_195117.png" src="https://qiita-image-store.s3.amazonaws.com/0/226419/3a66c896-8a82-e343-3f65-314c9b71929a.png">

Me.Close()でプログラムを終了しています。

#ソースコード全体
Public Class Form1

    Dim con, calc(2) As Double 'データ引き込み用と計算用の入れ物
    Dim concentration(3), er_msg As String　'濃度指定の入れ物とエラーメッセージの入れ物
    Dim ric, conti As Integer '行動管理用の入れ物
    Dim style_cr As MsgBoxStyle 'メッセージ用の設定

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

        Select Case ComboBox1.Text '変換元のデータ指定

            Case concentration(0) 'ppm

                ric = 1

            Case concentration(1) 'ppb

                ric = 2

            Case concentration(2) 'ppt

                ric = 3

            Case concentration(3) 'ppq

                ric = 4

        End Select

        If ric = 1 Then 'ppm

            con = Val(TextBox1.Text)

            If Not (System.Text.RegularExpressions.Regex.IsMatch(TextBox1.Text, "[^0-9.]")) And con > 0 Then

                conti = 1

            Else

                conti = 0
                MsgBox(er_msg, style_cr, "処理不能な値")

            End If

            CA()

            TextBox2.Text = calc(2) * 10 ^ (calc(1) + 3) 'ppm => ppb
            TextBox3.Text = calc(2) * 10 ^ (calc(1) + 6) 'ppm => ppt
            TextBox4.Text = calc(2) * 10 ^ (calc(1) + 9) 'ppm => ppq
            con = 0

            If conti = 0 Then 'エラー時にエラーと表示させる

                ABAL()

            End If

        ElseIf ric = 2 Then 'ppb

            con = Val(TextBox2.Text)

            If Not (System.Text.RegularExpressions.Regex.IsMatch(TextBox2.Text, "[^0-9.]")) And con > 0 Then

                conti = 1

            Else

                conti = 0
                MsgBox(er_msg, style_cr, "処理不能な値")

            End If

            CA()

            TextBox1.Text = calc(2) * 10 ^ (calc(1) - 3) 'ppb => ppm
            TextBox3.Text = calc(2) * 10 ^ (calc(1) + 3) 'ppb => ppt
            TextBox4.Text = calc(2) * 10 ^ (calc(1) + 6) 'ppb => ppq
            con = 0

            If conti = 0 Then 'エラー時にエラーと表示させる

                ABAL()

            End If

        ElseIf ric = 3 Then 'ppt

            con = Val(TextBox3.Text)

            If Not (System.Text.RegularExpressions.Regex.IsMatch(TextBox3.Text, "[^0-9.]")) And con > 0 Then

                conti = 1

            Else

                conti = 0
                MsgBox(er_msg, style_cr, "処理不能な値")

            End If

            CA()

            TextBox1.Text = calc(2) * 10 ^ (calc(1) - 6) 'ppt => ppb
            TextBox2.Text = calc(2) * 10 ^ (calc(1) - 3) 'ppt => ppm
            TextBox4.Text = calc(2) * 10 ^ (calc(1) + 3) 'ppt => ppq
            con = 0

            If conti = 0 Then 'エラー時にエラーと表示させる

                ABAL()

            End If

        ElseIf ric = 4 Then 'ppq

            con = Val(TextBox4.Text)

            If Not (System.Text.RegularExpressions.Regex.IsMatch(TextBox4.Text, "[^0-9.]")) And con > 0 Then

                conti = 1

            Else

                conti = 0
                MsgBox(er_msg, style_cr, "処理不能な値")

            End If

            CA()

            TextBox1.Text = calc(2) * 10 ^ (calc(1) - 9) 'ppq => ppb
            TextBox2.Text = calc(2) * 10 ^ (calc(1) - 6) 'ppq => ppm
            TextBox3.Text = calc(2) * 10 ^ (calc(1) - 3) 'ppq => ppt
            con = 0

            If conti = 0 Then 'エラー時にエラーと表示させる

                ABAL()

            End If

        Else 'ric=0　つまり未選択

            MsgBox("変換元のデータが指定されていません", style_cr, "変換元不明")
            Exit Sub

        End If

    End Sub

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        TACL() '初期化操作

        concentration(0) = "ppm"
        concentration(1) = "ppb"
        concentration(2) = "ppt"
        concentration(3) = "ppq"

        For i = 0 To 3 Step 1 'コンボボックスにアイテムを追加する

            ComboBox1.Items.Add(concentration(i))

        Next i

        'メッセージボックスのスタイル設定
        style_cr = MsgBoxStyle.Critical Or MsgBoxStyle.ApplicationModal Or MsgBoxStyle.MsgBoxSetForeground Or MsgBoxStyle.SystemModal
        er_msg = "適切な数値を入力してください。" & vbCrLf & "エラー内容:未入力、非数値、0以下の数値の入力"

    End Sub

    Private Sub Button3_Click_1(sender As Object, e As EventArgs) Handles Button3.Click '終了ボタン

        Me.Close()

    End Sub

    Private Sub TACL() '初期化操作部

        TextBox1.Text = ""
        TextBox2.Text = ""
        TextBox3.Text = ""
        TextBox4.Text = ""
        ComboBox1.Text = "変換元の濃度を指定"

        con = 0
        ric = 0
        conti = 0

    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click '初期化ボタン

        TACL()

    End Sub

    Private Sub CA() '計算処理部

        If con > 0 Then '桁数判断

            While con > 10 '10より大きいならば10で割って指数+1

                con /= 10
                calc(1) += 1

            End While

        Else

            While con >= 1 '1以下ならば10倍して指数-1

                con *= 10
                calc(1) -= 1

            End While

        End If

        calc(2) = con / 10 ^ (calc(1)) '係数の算出

    End Sub

    Private Sub ABAL() 'すべてのTextBoxにエラーと表示せよ

        TextBox1.Text = "Error"
        TextBox2.Text = "Error"
        TextBox3.Text = "Error"
        TextBox4.Text = "Error"

    End Sub

End Class

#最後に
久しぶりのプログラミングだったので、「あれ？ここのコードってどうやって書くんだったっけ？」となりました。

やっぱり書かないと忘れますね。特に正規化のあたりとか。

次回は規定濃度の溶液をこの量作るためには原液が何mL必要かを計算するプログラムや何倍希釈・濃縮なのかを計算するようなプログラムを作ろうかなと考えています。

このプログラムがいつの日か、どこかの誰かの役に立つといいな。