電卓の小数点表示について
投稿者 魔界の仮面弁士  (社会人)
投稿日時
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 などを正確には表現できません。
½、¼、¾、⅝ といった値であれば誤差なく保持できますが…。
たとえば 1÷3 という計算結果を小数として保持する場合、
3 進小数であれば、小数点以下 1 桁あれば割り切れますが、
10進小数だと0.333333…という循環小数になるため、
有限の桁で表そうとすると誤差が生じてしまい、
本来の値よりも少し小さい値になります。
それと同様、1÷10 という計算結果を小数として保持する場合、
10 進小数ならば割り切れますが、
2 進小数の場合、有限の桁で表そうとすると誤差が生じることになります。
それが、上記のサンプルで 10 回足しても 1.0 に戻らなかった理由です。
というわけで、内部的に 2 進数で管理される Double 型の代わりに、
10進数管理の Decimal 型をお奨めします。
扱える値の範囲が Double よりは狭くなりますが、一般的な電卓なら十分かと。
まず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 の内部値は異なるのですが、値としては同一視されます。
その他、桁数上限を超えて数字キーが押された場合に無視する処理とか、
四則演算子を複数回入力された場合の動作とか、電卓を作る場合は
色々と考えるべき点が多いですね。
> 小数点を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 の内部値は異なるのですが、値としては同一視されます。
その他、桁数上限を超えて数字キーが押された場合に無視する処理とか、
四則演算子を複数回入力された場合の動作とか、電卓を作る場合は
色々と考えるべき点が多いですね。
四則演算ができ、小数点の計算にも対応するもの
(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