- 締切済み
Excel Vbaの関数Intの機能について
Excel Vbaでの関数Intの機能について教えてください ある特定の値についての処理が私の予想に反した動きをします。私の理解が浅いと思うのですがその理由が分かりません。 Vbaでプロシジャーを作ってみました。ともに小数0.3を10倍し、 その値とIntの値を比較しています。コードの違いは(1)と(2)です。 Public Sub Test1() Dim dblNum1 As Double, dblNum2 As Double dblNum1 = 0.2 dblNum1 = dblNum1 + 0.1 ……(1) dblNum2 = dblNum1 * 10 Debug.Print "Int(dblNum2) = " & Int(dblNum2); " : dblNum2 = " & dblNum2 Debug.Print "差(Test1):" & dblNum2 - Int(dblNum2) End Sub Public Sub Test2() Dim dblNum1 As Double, dblNum2 As Double dblNum1 = 0.3 ' dblNum1 = dblNum1 + 0.1 ……(2)コメントにしています dblNum2 = dblNum1 * 10 Debug.Print "Int(dblNum2) = " & Int(dblNum2); " : dblNum2 = " & dblNum2 Debug.Print "差(Test2):" & dblNum2 - Int(dblNum2) End Sub 実行結果は次のようになりました。 Test1の結果 Int(dblNum2) = 3 : dblNum2 = 3 差(Test1):4.44089209850063E-16 Test2の結果 Int(dblNum2) = 3 : dblNum2 = 3 差(Test2):0 差(Test1)がなぜ0にならないのか不思議な気がします(当たり前なのかな?)。 0.8についても同様な結果になります。 その他の値0.1 ~ 0.9では、私の予想通り、差(Test1)、差(Test2)ともに0になります。 ネットなどで検索したら、浮動小数点の扱いに関係がありそうだなのかなぐらいは想像できるのですが具体的な仕組みはよく分かりません。 この疑問に至った背景について、説明します。 Int関数の機能については特段興味はなかったのですが、「有限小数の小数点以下の桁数を調べる関数」を作るのに利用していました。その関数では、なぜか特定の小数についてはうまく機能しませんでした。調べていくうちに上記のような現象に気づきました。 話がそれて厚かましいお願いになりますが、「有限小数の小数点以下の桁数を調べる関数」のコーディングの例を教えていただけないでしょうか。私が作った関数は以下のようになります。これについてもアドバイスをいただければ大変助かります。 Public Function FiniteDCPlace(dblR As Double, intMax As Integer) As Integer '機能:小数点以下の何桁の有限小数になるか、その桁数を調べる。 '引数:dblRは小数、intMax は調べる最大の桁数 ただしintMaxは7以下の値 '戻り値:小数点以下の桁数がintMax以下の有限小数の場合、その桁数 ' それ以外は「-1」 Dim i As Integer Dim dblNum As Double If intMax > 7 Then FiniteDCPlace = -1 Exit Function Else 'No Ope End If dblNum = dblR FiniteDCPlace = -1 For i = 0 To intMax Debug.Print "Int(dblNum) = " & Int(dblNum) & " : dblNum = " & dblNum Debug.Print "差(FiniteDCPlace):" & dblNum - Int(dblNum) If Int(dblNum) = dblNum Then FiniteDCPlace = i Exit For Else dblNum = dblNum * 10 FiniteDCPlace = -1 End If Next End Function 次のプロシジャーでチェックしました。 小数 0.028 のとき3ではなく、-1を返します。 Private Sub testFiniteDCPlace() Dim intNum As Integer Dim dblNum As Double Dim i As Integer dblNum = 0.028 ‘予想外の結果になる intNum = FiniteDCPlace(dblNum, 7) Debug.Print dblNum & " の桁数 : " & intNum dblNum = 0.027 ‘予想通りの結果になる 3になる intNum = FiniteDCPlace(dblNum, 7) Debug.Print dblNum & " の桁数 : " & intNum End Sub ちなみに、環境はOS Widows10 、Excel2013 です。 以上よろしくお願いします。 質問の内容を書いてください
- みんなの回答 (4)
- 専門家の回答
みんなの回答
- real beatin(@realbeatin)
- ベストアンサー率82% (174/211)
回答No.2-3 です。何度もすみません。 前スレで、> ちなみに小数は8桁以内の整数と8桁以内の整数の商(割り算)になります。 とご説明があったのを念頭に(というより思い込みで)応えていましたが、 一応、もっと桁数が多い場合の汎用性を考えると、 オーバーフローを回避する意味では、(No.2のCLng()よりも)そもそもの、 Int()関数を使った方が簡単で良かったのかも知れません。 なので、Int()関数を使った直接的な回答を。 ' ' /// 十進(固定)小数点数に変換、整数に丸めた数値との文字長の差を求める /w9076681 Public Function FiniteDCPlaceA(ByVal numDec As Variant, Optional ByVal lngMax As Long = 7&) As Long If lngMax > 7& Then FiniteDCPlaceA = -1& Else numDec = CDec(numDec) FiniteDCPlaceA = Len(numDec - Int(numDec)) - 2 If FiniteDCPlaceA < 0 Then FiniteDCPlaceA = 0 If FiniteDCPlaceA > lngMax Then FiniteDCPlaceA = -1 ' ? それ以外は「-1」 ? End If End Function ' ' /// Private Sub testFiniteDCPlaceA() Dim lngNum As Long Dim dblNum As Double dblNum = 0.28 lngNum = FiniteDCPlaceA(dblNum) Debug.Print dblNum & " の小数点以下の桁数 : " & lngNum dblNum = 0.27 lngNum = FiniteDCPlaceA(dblNum) Debug.Print dblNum & " の小数点以下の桁数 : " & lngNum End Sub ' ' /// ついでに、回答No.2で触れた"小数点の位置"版もあげておきます。 この場合は整数に変換する(丸める)必要がありません。 ' ' /// 十進(固定)小数点数に変換後、小数点数の文字長と小数点の位置との差を求める /w9076681 Public Function FiniteDCPlaceJ(ByVal numDec As Variant, Optional ByVal lngMax As Long = 7&) As Long Dim lngDecPointPos As Long If lngMax > 7& Then FiniteDCPlaceJ = -1& Else numDec = CDec(numDec) lngDecPointPos = InStr(numDec, ".") If lngDecPointPos Then FiniteDCPlaceJ = Len(numDec) - lngDecPointPos If FiniteDCPlaceJ > lngMax Then FiniteDCPlaceJ = -1 ' ? それ以外は「-1」 ? End If End Function ' ' /// Private Sub testFiniteDCPlaceJ() Dim lngNum As Long Dim dblNum As Double dblNum = 0.28 lngNum = FiniteDCPlaceJ(dblNum) Debug.Print dblNum & " の小数点以下の桁数 : " & lngNum dblNum = 0.27 lngNum = FiniteDCPlaceJ(dblNum) Debug.Print dblNum & " の小数点以下の桁数 : " & lngNum End Sub ' ' /// 他にも方法はたくさんあると思いますが、 私の場合は何れにしてもCDec()関数(場合によってはCCur()関数)を使います。 それから、前回のご質問の時から気になっていたのですが、 セルに表示されている(数値ではなくて)数字(文字列)を取得する range.Text プロパティの扱い方を確認しておくと、 もしかしたら何かの役に立つこともあるのかも知れません。 Cells(1, 1).Text Range("A1").Text などのように、単一セルに対して用います。 、、、見当外れでしたら、この件は無視してください。 意図にそぐわないとか、不足や疑問が残るようでしたら、補足欄にでも書いてみて下さい。以上です。
- real beatin(@realbeatin)
- ベストアンサー率82% (174/211)
解答No.2、補足修正です。 /// > 浮動小数点数を相互に(四則)演算させる場合には、 > 基本的に演算誤差が出ます。 > なのでVBAでは、二進数を十進数Variant/Decimalに変換してから演算します。 > > それでも、厳密には誤差が発生する場合もあるにはあるのですが、 > 7桁という縛りがあるるようですから問題なく扱える、と言って良いでしょう。 ↓ 【二進数である】浮動小数点数を相互に(四則)演算させる場合には、 基本的に演算誤差【(丸め誤差)】が出ます。 なので【今回必要とされる関数については】VBAでは、 二進数【浮動小数点数】を 十進数【小数点数】Variant/Decimalに変換してから 演算【させる必要があります】。 それでも、厳密には【丸め】誤差が発生する場合もあるにはあるのですが、 【今回必要とされる関数については】 7桁という縛りがあるるようですから問題なく扱える、と言って良いでしょう。 /// 以上、誤解を招き易い表記がありましたので、読み替えてあげてください。
- real beatin(@realbeatin)
- ベストアンサー率82% (174/211)
こんにちは。 浮動小数点数は、Double型だけではありません。 Single、Double、Currency、Date、Variant/Decimal なので、引数として受ける場合はVariant型が必須です。 ただ、そもそもがExcelのセルの値だけを判別する目的なのでしたら、 それは、Double、Currency、Dateの3種類だけ相手にすればいいことになりますが、 Currency、DateをDouble型の引数で受ければ、それだけでも誤差が生じることになります。 Excelのセルの値だけを判別する目的で考えたら、 Excelのシート上で、Excel関数を使った数式で計算させた方が一層簡単でしょうから、 場合によってはVBAからシート上で計算させる方法もある、 ということを念頭に入れておいてください。 #ただし、Excelのシート上でも、浮動小数点数相互の(四則)演算については、 精度を期待できるものではありません。桁数を求めるならば、という話です。 浮動小数点数を相互に(四則)演算させる場合には、 基本的に演算誤差が出ます。 なのでVBAでは、二進数を十進数Variant/Decimalに変換してから演算します。 それでも、厳密には誤差が発生する場合もあるにはあるのですが、 7桁という縛りがあるるようですから問題なく扱える、と言って良いでしょう。 そちらで整数部分の桁数を求める為にInt()関数を使っている部分を、 Clng()関数とAbs()関数の組合わせにしています。 十進整数と十進小数点数との文字長の差を求めます。 どうせ文字列を扱うのなら、十進小数点数だけを相手にして、 文字長と小数点の位置を比較した方が効率的(整数を扱う必要はない)ですが、 この点については、敢えてご提示のものを踏襲します。 ※プロシージャ名、替えています。 ' ' /// Public Function FiniteDCPlace0(ByVal numDec As Variant, Optional ByVal lngMax As Long = 7&) As Long If lngMax > 7& Then FiniteDCPlace0 = -1& Else numDec = CDec(numDec) FiniteDCPlace0 = Len(Abs(numDec - CLng(numDec))) - 2 If FiniteDCPlace0 < 0 Then FiniteDCPlace0 = 0 If FiniteDCPlace0 > lngMax Then FiniteDCPlace0 = -1 ' ? それ以外は「-1」 ? End If End Function ' ' /// Private Sub testFiniteDCPlace0() Dim lngNum As Long Dim dblNum As Double dblNum = 0.28 lngNum = FiniteDCPlace0(dblNum) Debug.Print dblNum & " の小数点以下の桁数 : " & lngNum dblNum = 0.27 lngNum = FiniteDCPlace0(dblNum) Debug.Print dblNum & " の小数点以下の桁数 : " & lngNum End Sub
- dogs_cats
- ベストアンサー率38% (278/717)
専門家では無く、バージョンも2007ですので、ご参考まで。 浮動小数点で結果が得られない時は、変数を文字列変換後数値に戻すと正常値を得られる事があります。 Public Sub Test1() Dim dblNum1 As Double, dblNum2 As Double dblNum1 = 0.2 dblNum1 = Val(CStr(dblNum1 + 0.1))で解消しました。 dblNum = dblRは dblNum = Val(CStr(dblR))やdblNum = Val(CStr(dblNum))でも解消しませんでした。 intとは関係ない10倍した箇所を修正した所、正常値となりました。 dblNum = Val(CStr(dblNum)) * 10 dblNum = 0.028でしか試していませんので、他のdblNumもVal(CStr(dblNum)) に変更すべきかは他の数値での結果で判断下さい。
お礼
Dogs_cats さん 回答ありがとうございます。 >浮動小数点で結果が得られない時は、変数を文字列変換後数値に戻す・・・ 参考になりました。アドバイスいただいた修正は自分でも確認できました。 型変換の関数をほとんど使ったことがなかったので、Vba のヘルプ・MSDNのホームページなどであらためて調べてみました。いろいろな関数があることが分かり、勉強になりました。関数Val,CStr についても機会があれば使っていき理解を深めていきたいと思います。 お礼が遅くなりましたが、ありがとうございました。
お礼
realbeatin 様 お礼が遅くなりました。 質問内容がタイトルから外れたものになったにもかからわらず、丁寧に回答をいただきありがとうございます。小数点以下の桁数を求める関数3つともうまくいきました。「小数点の位置」版が一番分かりやすいように思いました。自分もMSDNの「データ型のトラブルシューティング(Visual Basic)-浮動小数点式での比較が等しくならない」を参考に修正版を作り、うまくいきました。 今回の回答で勉強させてもらったこと。 >二進数【浮動小数点数】を十進数【小数点数】Variant/Decimalに変換してから・・・ そもそも、Decimal(10進数型)を知りませんでした。(はるか昔に勉強した?) 型変換の関数についてもほとんど知識がありませんでした。 どの場面でどのように使えばいいのかの経験を積んでいきたいと思います。 >それから、前回のご質問の時から気になっていたのですが、 >セルに表示されている(数値ではなくて)数字(文字列)を取得する >range.Text プロパティの扱い方を確認しておくと、 >もしかしたら何かの役に立つこともあるのかも知れません。 セルに表示されている(数値)を計算処理しないのであれば(文字列)として扱うという発想が全くありませんでした。桁数を求める関数でも同様でした。数値の場合はrange.Value プロパティしか使っていませんでした。 今回の質問のきっかけは、生徒が演習した結果を自動採点する教材を作る中での疑問でした。「表示形式->小数点以下の3位の設定」の判定で正解と生徒答案のNmberFormatLocalを比較していました。計算結果がたまたま有限小数3位のセルがあり、生徒が書式設定をしなくて「標準/G」のままで、チェックがうまくいきませんでした(チェックから「標準/G」を外していた)。授業の課題が「書式設定」の場合は、従来通りNumberFormatLocalを利用する。 印刷結果を重視(見た目がすべて)する場合はrange.Textを利用することにしました。 浮動小数点の精緻な数値計算については特に興味はなっかたのですが、疑問をすぐに解消したくて、本来の目的からそれた質問をしてしまいました。 realbeatin 様 3度もお世話になりました。 3回の質問とも「そのことが分かってそれが、なんやねん。そもそもお前は何がしたいねん。」と突っ込まれそうな内容にも関わらず丁寧に対応していただき本当にありがとうございました。