tagCANDY CGI VBレスキュー(花ちゃん)の Visual Basic 6.0用 掲示板 [ツリー表示へ]   [Home]
一括表示(VB6.0)
タイトルEXCEL 計算結果が指数表記になる。
記事No16609
投稿日: 2021/04/13(Tue) 20:13
投稿者数学が苦手なプログラマ
いつも利用させていただいています。
早速ですが質問です。
当方は現在、EXCELのマクロ開発を生業としている
プログラマです。
ユーザーフォームにテキストボックスを配置して
入力された数値を計算するというのを作っているのですが
計算結果が指数表記のようなってしまっています。
簡単に説明すると、
まず、ユーザーフォームにテキストボックスを3個配置します。
テキストボックス1に「-1.09」と入力。
テキストボックス2に「1」と入力したら
テキストボックス3に計算結果が「-9.0000000001E-2」という
表記(0の個数があっていないかもしれませんが)
になってしまいます。
他の数値、例えばテキストボックス1に「-4.09」
テキストボックス2に「1」と入力すると
計算結果は、「-3.09」と出てきます。
なぜすなるのかわからず困っています。どなたかお教えいただけると
幸いです。
EXCELのバージョンは「365」を使用しています。
ちなみに、計算式は
Val(テキストボックス1)+Val(テキストボックス2)としていて
テキストボックス2のチェンジイベントに書いています。
よろしくお願いします。

[ツリー表示へ]
タイトルRe: EXCEL 計算結果が指数表記になる。
記事No16610
投稿日: 2021/04/14(Wed) 22:14
投稿者魔界の仮面弁士
> 計算結果が指数表記のようなってしまっています。

小数を含む演算時には、常に【データ型】を意識するようにしましょう。

テキストボックスの値は「文字列」であって「数値」ではないので、
計算前に、明示的にデータ型を合わせる必要があるはずですよね。


'文字列連結として扱われ、文字列型の "-1.091" になる。
Debug.Print "-1.09" + "1"

'十進型同士の加算として扱われ、十進型の「-0.09」 になる。
Debug.Print CDec("-1.09") + CDec("1")

'通貨型同士の加算として扱われ、通貨型の「-0.09」 になる。
Debug.Print CCur("-1.09") + CCur("1")

'倍精度浮動小数型同士の加算として扱われ、倍精度浮動小数型の「-9.00000000000001E-02」 になる。
Debug.Print CDbl("-1.09") + CDbl("1")

'単精度浮動小数型同士の加算として扱われ、単精度浮動小数型の「-9.000003E-02」 になる。
Debug.Print CSng("-1.09") + CSng("1")


> Val(テキストボックス1)+Val(テキストボックス2)としていて
MsgBox TypeName( Val("-1.09") )
を実行すると分かるかと思いますが、Val 関数の結果は 倍精度浮動小数型 です。

そして Single や Double は、二進小数で管理されるデータ型です。
そのため、0.50 や 0.75 といった値であれば誤差なく表せますが、
0.01 や 0.09 といった値は、どうしても誤差を含んでしまいます。


1÷3 という値は、三進小数であれば正確に表現できますが、
十進小数では 0.3333333333333…という循環小数になり、有限の桁数では誤差を生じます。

それと同様、1÷10 という値は、十進小数であれば正確に表現できますが、
二進小数では 0.000110011001100110011… という循環小数になってしまうのです。


無限の桁数を用意するわけにはいかないので、有効桁数を超えた分は
途中の桁で打ち切られます。それが微細な誤差として現れている理由です。


ちなみに十進型(Decimal)は、文字通り十進数でデータを保持するため、
この手の誤差が生じません。(その分、メモリ消費量や処理速度の点で劣ります)

そして通貨型(Currency)は、小数部は 4 桁固定という制限があるものの、
内部的には 64bit 整数型であるため、やはり小数部の誤差が出ません。

まぁ、Decimal や Currency が誤差が出ないとは言っても、
もちろん、1÷3 に対する打ち切り誤差などはあるのですけれどね。



なお、演算式によってもデータ型が変化することがあります。

' ほとんどの割り算は、Double 型として演算されます
Debug.Print TypeName( CLng("1") / CLng("1") )
Debug.Print TypeName( CInt("1") / CInt("1") )

' Deciaml 型同士の割り算は、Decimal 型となります
' Currency 型同士の割り算は、Currency 型となります
' Single 型同士の割り算は、Single 型となります
Debug.Print TypeName( CDec("1") / CDec("1") )
Debug.Print TypeName( CCur("1") / CCur("1") )
Debug.Print TypeName( CSng("1") / CSng("1") )

' \ 演算子による除算は、VBA では整数型になります (VB.NET の場合には、整数以外にもなりえます)
Debug.Print TypeName( CByte("1") \ CByte("1") )
Debug.Print TypeName( CInt("1") \ CInt("1") )
Debug.Print TypeName( CLng("1") \ CLng("1") )
Debug.Print TypeName( CLngLng("1") \ CLngLng("1") )


' Int 関数や Fix 関数は整数値を返しますが、データ型までは変化しないので気を付けましょう
Debug.Print TypeName( Fix(CSng("2.5")) )
Debug.Print TypeName( Fix(CDbl("2.5")) )
Debug.Print TypeName( Fix(CCur("2.5")) )
Debug.Print TypeName( Fix(CDec("2.5")) )
Debug.Print TypeName( Int(CSng("2.5")) )
Debug.Print TypeName( Int(CDbl("2.5")) )
Debug.Print TypeName( Int(CCur("2.5")) )
Debug.Print TypeName( Int(CDec("2.5")) )


' Val 関数は、渡す文字列によっては実行時エラーになる可能性があるため、
' Val といえども、変換前に文字列の内容を精査しておくことが望ましいです。
' (あるいは変換時のエラーに対処できるよう、On Error を併用するようにします)
Debug.Print Val("12.345d3&21") ' 12345
Debug.Print Val("12.3456d3&21") '変換エラー
Debug.Print Val("1.5%") '変換エラー

[ツリー表示へ]
タイトルRe^2: EXCEL 計算結果が指数表記になる。
記事No16611
投稿日: 2021/04/15(Thu) 09:34
投稿者魔界の仮面弁士
先に述べた通り、指数表記を避けたければ、CDec 関数をお奨めしますが、
普段使うのは Double 型の方でしょうから、もう少し補足説明を。

Single や Double といった「浮動小数点数」を扱う以上は、
【有効桁数】を超える部分は誤差の範疇として、
運用上には誤差部分を無視して扱う事が求められます。

それぞれのデータ型の精度について調べておくことをお奨めします。


>> テキストボックス3に計算結果が「-9.0000000001E-2」という
>> 表記(0の個数があっていないかもしれませんが)
>
> '倍精度浮動小数型同士の加算として扱われ、倍精度浮動小数型の「-9.00000000000001E-02」 になる。
> Debug.Print CDbl("-1.09") + CDbl("1")

この時、E の前に数字が 15 個ありますよね。
これは Double 型の有効桁数が 1+52bit であることに起因しています。

最大桁が 53 bit の二進数というのは、十進数に換算すると 53×Log10(2)≒15.95458977 桁相当です。



> '単精度浮動小数型同士の加算として扱われ、単精度浮動小数型の「-9.000003E-02」 になる。
> Debug.Print CSng("-1.09") + CSng("1")

この時、E の前に数字が 7 個ありますね。
これは Single 型の有効桁数が 1+23bit であることに起因しています。

Single の有効桁数は、十進数でいうと 24×Log10(2)≒7.2247198959 相当です。


さて今回「-9.00000000000001E-02」と表示されてしまった Double 値ですが、
この内部表現である二進小数値を、そのまま十進小数に変換した場合には
 -0.0900000000000000799360577730112709105014801025390625
という値になります。

一方、CDbl("-9.00000000000001E-02") という値を求めてみると、こちらは
 -0.09000000000000009381384558082572766579687595367431640625
という、また別の値となります。僅かな差ですけれどね。


先に述べた通り、Double 型の有効桁数は 16 桁弱しかありませんので、
後半の細かい数値部には、さほど意味などありません。

これらはいずれもデバッガ上では近似値表現となり、
違いに気が付きにくいかと思いますが、参考までに
手元の環境で実験した結果を貼っておきますね。

Dim af2 As Double: af2 = CDbl("-0.09000000000000006605826996519681415520608425140380859375") ' af1 の次に大きな値
Dim af1 As Double: af1 = CDbl("-0.09000000000000007993605777301127091050148010253906250000") ' dbl の次に大きな値
Dim dbl As Double: dbl = CDbl("-0.09000000000000009381384558082572766579687595367431640625") ' = -9.00000000000001E-02
Dim bf1 As Double: bf1 = CDbl("-0.09000000000000010769163338864018442109227180480957031250") ' dbl の次に小さな値
Dim bf2 As Double: bf2 = CDbl("-0.09000000000000012156942119645464117638766765594482421875") ' bf1 の次に小さな値

' デバッガ上では、有効桁数の範囲で近似値表現されます。
Debug.Print af2  ' VBA では「-9.00000000000001E-02」表記、.NET では「-0.090000000000000066」表記
Debug.Print af1  ' VBA では「-9.00000000000001E-02」表記、.NET では「-0.09000000000000008」表記
Debug.Print dbl  ' VBA では「-9.00000000000001E-02」表記、.NET では「-0.0900000000000001」表記
Debug.Print bf1  ' VBA では「-9.00000000000001E-02」表記、.NET では「-0.090000000000000108」表記
Debug.Print bf2  ' VBA では「-9.00000000000001E-02」表記、.NET では「-0.090000000000000122」表記

' VBA 上では「-9.00000000000001E-02」という同一表記に見えますが、
' それでも内部的には別の値であり、大小関係も維持されています。
Debug.Print (af2 = af1), (af2 > af1), (af2 - af1)
Debug.Print (af1 = dbl), (af1 > dbl), (af1 - dbl)
Debug.Print (dbl = bf1), (dbl > bf1), (dbl - bf1)
Debug.Print (bf1 = bf2), (bf1 > bf2), (bf1 - bf2)



さらに言えば、CDbl("-0.09") という値の内部値は
 -0.0899999999999999966693309261245303787291049957275390625
にあたります。こちらも前後値を載せておきます。

2 つ後: -0.08999999999999996891375531049561686813831329345703125000
1 つ後: -0.08999999999999998279154311831007362343370914459228515625
元の値: -0.08999999999999999666933092612453037872910499572753906250
1 つ前: -0.09000000000000001054711873393898713402450084686279296875
2 つ前: -0.09000000000000002442490654175344388931989669799804687500


Integer 型では、1.5 という値を表現できずに 1 または 2 に丸められるのと同様、
Double 型で 0.09 という値を表現しようとした場合、格納可能な精度で丸められてしまい、
表現可能な範囲の中で最も近い値(すなわち近似値)として扱われることになります。


有効桁数を引き上げたいのであれば、CDec 関数を使ってみてください。
Decimal の有効桁数は 96bit (十進数換算で 28.89887958 桁相当)あるので、
Double 型の 15.95458977 桁よりも誤差を減らすことができます。


もちろん CDec(Val(TextBox1.Value)) などとするのは駄目ですよ。

[ツリー表示へ]