電卓の小数点表示について

タグの編集
投稿者 mmmzk  (社会人) 投稿日時 2023/9/5 11:53:05
Visual Studio2019(VB.net)のフォームアプリで電卓作成しています。
四則演算ができ、小数点の計算にも対応するもの
(VBを始めて約2週間ド初心者)


表示や計算自体に特に問題はありませんでしたが、
小数点ボタンを連続で2回クリックすると、
下から3行目のCDbl(TextBox1.Text)部分にエラーが出てしまいます。

【エラー内容】
'String "1.." から型 'Double' への変換は無効です
↑入力した1..が数値と認められず、数値に変換して代入できない

エラーの意味は理解できたつもりですが、
対処するコードをどう書けばいいか悩んでいます。

具体的な対処法としては、
小数点がクリックされたタイミングを起点にし、
小数点を2回クリックされた場合に対して処理をする
というのがいいかと思い色々試しましたがうまくいきません。

ご助言いただけますと幸いです。
以下が現状作成しているコードになります。



    '数字・小数点ボタンクリック時処理
    Private Sub CM_Num(intKey As Integer)

        If iType = OP_ERROR Then Exit Sub

        If lastKey <> KEY_NUMBER Then '直前に押されたキーが数字以外(演算子キー押した)
            TextBox1.Text = "0" '表示値を0に

        End If


        If intKey = -1 Then  '今回入力値が小数点である
            TextBox1.Text = TextBox1.Text & "." '現在の表示値に小数点を追加
        Else
            If TextBox1.Text = "0" Then '元から0の場合
                    TextBox1.Text = CStr(intKey) '今回入力値を表示する
                Else
                    TextBox1.Text = TextBox1.Text & CStr(intKey) 'それ以外(0-9)の場合は入力値を表示する
                End If


            End If

        cNum = CDbl(TextBox1.Text) '数値に変換して代入
        lastKey = KEY_NUMBER '直前に押された数字キーを記憶

    End Sub
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/9/5 13:16:23
> 下から3行目のCDbl(TextBox1.Text)部分にエラーが出てしまいます。

まず1点目。
CDbl ではなく、Double.TryParse や Decimal.TryParse を使いましょう。

TryParse メソッドは、「数値に変換できなかった場合」はエラーになるのではなく、
変数に数値をセットすると同時に、変換の成否を True / False で返しつ、
かつ、変換できなかった時には変数の値をゼロにするというものです。


TryParse メソッドは、ここのサイトだと投稿プログラム[BMI測定]の解説などで使われています。
http://rucio.o.oo7.jp/main/Toukou/T9_BMI.htm

初級講座だと、第16回の 2-13 あたり。
http://rucio.o.oo7.jp/main/dotnet/shokyu/standard16.htm



2点目。

Double 型よりも Decimal 型の方がおすすめです。
TextBox1.Text だと文字列型になるため、数値型への変換が必要ですが、
NumericUpDown1.Value からであれば、最初から数値型である Decimal として結果が得られます。

また、NumericUpDown コントロールならば不正な数値が入力される心配もありません。
NumericUpDown には、入力可能な最小値/最大値を設定するプロパティや、
小数点以下の桁数を指定するためのプロパティや、三桁区切りのカンマの有無なども設定できます。


3点目。

上記で Decimal を推す理由ですが、
これは Double が誤差を含みやすいデータ型だからです。

これは、Double の内部表現が 2進小数であることに由来しています。
電卓で使うのは通常、 10進数表記であるはず。

たとえば 2進小数 は、1÷10 などを正確には表現できません。
½、¼、¾、⅝ といった値であれば誤差なく保持できますが…。

Dim a As Double = CDbl("0.1")
Dim b As Decimal = CDec("0.1")

'0.1 を 10 回足す 
Dim x As Double = a + a + a + a + a + a + a + a + a + a
Dim y As Double = b + b + b + b + b + b + b + b + b + b

If x = 1.0R Then   '1.0R や 1.0# は、「Double 型の 1.0」を意味する表記です。 
    MsgBox("a を 10 回足すとは 1.0 です")
ElseIf x < 1.0R Then
    MsgBox("a を 10 回足した値は 1.0 未満です")    ' ここに入る 
Else
    MsgBox("a を 10 回足した値は 1.0 より大きいです")
End If

If y = 1.0D Then   '1.0D や 1.0@ は、「Decimal 型の 1.0」を意味する表記です。 
    MsgBox("b を 10 回足すとは 1.0 です")   ' ここに入る 
ElseIf y < 1.0R Then
    MsgBox("b を 10 回足した値は 1.0 未満です")
Else
    MsgBox("b を 10 回足した値は 1.0 より大きいです")
End If



たとえば 1÷3 という計算結果を小数として保持する場合、
3 進小数であれば、小数点以下 1 桁あれば割り切れますが、
10進小数だと0.333333…という循環小数になるため、
有限の桁で表そうとすると誤差が生じてしまい、
本来の値よりも少し小さい値になります。


それと同様、1÷10 という計算結果を小数として保持する場合、
10 進小数ならば割り切れますが、
2 進小数の場合、有限の桁で表そうとすると誤差が生じることになります。
それが、上記のサンプルで 10 回足しても 1.0 に戻らなかった理由です。


というわけで、内部的に 2 進数で管理される Double 型の代わりに、
10進数管理の Decimal 型をお奨めします。
扱える値の範囲が Double よりは狭くなりますが、一般的な電卓なら十分かと。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/9/5 13:51:11
> 小数点がクリックされたタイミングを起点にし、
> 小数点を2回クリックされた場合に対して処理をする
> というのがいいかと思い色々試しましたがうまくいきません。

現状通り、TextBox1.Text に書き込んでおけば、
 If TextBox1.Text.Contains(".") Then
で、"." の有無を判断することができます。


あるいは既に小数点が打たれたかどうかを管理するための
Boolean 型のフィールド変数を作るということもできます。

[.] が押された場合は、このフィールドを True に変更します。
[AC]、[C]、[CE]、[=]、[+]、[-]、[*]、[/] が押された場合は、このフィールドを False にリセットします。
[0]~[9] などの場合は、このフィールドを書き換えません。

もしくは Boolean ではなく Integer で保持しておき、
何桁目に小数点記号があるのかを管理する…という実装にするケースもあります。



こうした特殊対応が必要なのは小数点記号に限りませんね。

たとえば、数値が 0 だったときに、[0] を押しても "00" や "000" にはなりません。
しかし、小数点記号を打った後だと、"0.0" や "0.00" と表示されます。


あるいはマイナス記号 [+/-] 。
これは数字の先頭に "-" の文字を付けたり外したりする目的がありますが、
現在の値が "0" だった場合は通常無視されますね。

※Double 型では、+0 と -0 の内部値は異なるのですが、値としては同一視されます。


その他、桁数上限を超えて数字キーが押された場合に無視する処理とか、
四則演算子を複数回入力された場合の動作とか、電卓を作る場合は
色々と考えるべき点が多いですね。