プロが教えるわが家の防犯対策術!

VBAのLog関数に関して質問です。

http://stabucky.com/wp/archives/3083

数値の桁数を求めるために
dd = Int(Log(ses) / Log(10) )+1

という計算を行いたいのですが
計算速度を上げるために、Log10を予め計算しておくことで
2倍の高速化ができることを確認しました。
dd = Log(ses) / 2.30258509299405

しかし、一見同じ結果を返すかと思ったのですがきりの良い数値を使うと
異なる結果が返ってきてしまいます。
【Int(Log(100) / 2.30258509299405 )+1】
2
【Int(Log(100) / log(10) )+1】
3

これはなぜなのでしょうか?
予め計算してあるlog(10)を使って計算するにはどうしたら良いですか?

質問者からの補足コメント

  • 納得しました。

    ご回答ありがとうございます。

    No.8の回答に寄せられた補足コメントです。 補足日時:2015/11/23 22:01

A 回答 (8件)

※ 要点


2.30258509299405 <> Log(10)
値が違えば、当然、計算結果もことなる。
対策は、「誤差を減らすための工夫をする」「別の手段を使う」

ーーーー
> 2.30258509299405というのはもちろんVBAで得られたものです。
> Dim www As Double
> www = Log(10)
> で得られます。


で、その得られた値が 2.30258509299405 だということを、どうやって確認したのか、ということです。


以下、Excelが手許に無いので、Pythonで代用しますが、おそらく同じことです。

log(10)を計算すると、Double 型では
1.0010011010111011000110111011101101010101010100010110 * 2^1
という整数1+小数52桁の2進小数として保存されます。これは、10 進法では**正確**に
2.302585092994045901093613792909309267997741699218750
です。

このうち、小数第52位には、打ち切りによる丸め誤差が含まれています。
10進で16桁以上は二進小数52位による影響を受ける箇所です。
そこで、実用として使える 15けたや16桁までを使い、それ以下は端数として使わない、ということがよくあります。
(例えば、CStr関数とか)

あなたが確認ものは、この「15 桁程度で端数処理した後」の値なのです。


さらに、
プログラムで書かれた定数 2.30258509299405 は、Double型としては
1.0010011010111011000110111011101101010101010100011111 * 2^1
であって、10進法では**正確**に
2.302585092994049897896502443472854793071746826171875
です。

2.30258509299405 と入力しても、2.30258509299405 にはなりません。
この値をDoubleで正確に表現することはでません。


以上のようなことから、log(10)と2.30258509299405は、同じ値ではありせん。
そのため
Log(100) / log(10) = 2.0
Log(100) / 2.30258509299405=1.999...
となり、intで整数にした結果、 2+1=3と、1+1=2になってしまいます。



繰り返しになりますが、

> Dim www As Double
> www = Log(10)

で求めたLog(10)でそのまま
Log(100) / www
とすれば
Log(100) /Log(10)
と同じ結果になるはずで。
※ 実行順や、変数の有効範囲(スコープ)には注意しましょう。



もっとも、これでも、完全に対策できるわけではありません。
必ず誤差が入ります。
    • good
    • 0

excelの数値の扱い方の説明を、No.2回答者kmeeさんがされているように、表示と内部数値とが異なるので、いくつかの値に関してはこの計算方式では、答えが狂ってしまいます。


https://support.microsoft.com/ja-jp/kb/78113

Excelのシートであれば、
http://stabucky.com/wp/archives/3083 の説明にある
「 桁数を表示させたいセル = ROUNDDOWN(LOG10(対象となる数値のあるセル),0)+1 」でも、ある範囲の数値に関して桁数を表示してくれます。
ただし、その場合も、例えば「9999999999999=14桁」「9999999999998=13桁」と異なる桁数を示します。「999999999999999000=19桁」と誤った桁を示します。

十進数の桁をカウントするならば、見た目が大事と考える方法も有効です。文字列とみなして長さを見れば良いのでしょう。
(対象の数値はB列にある Cells(i, 2)で選択できる として )

VBAの場合、私の環境で、(i10 = 2.30258509299405)として
「Cells(i, k) = Int(Log(Cells(i, 2)) / i10 + 1)」を4000回したところ
3.38秒でした。  
この場合、数値には0やマイナスはないとしています。
100、1000、10000などの桁数は間違って算出されます。

VBAの場合、私の環境で、(im = 10000000)として
「 iv = Cells(i, 2)
If iv < 0 Then iv = iv * -1
If iv < im Then
iv = iv & ".": Cells(i, k) = InStr(iv, ".") - 1
Else
iv = Fix(iv / im) & ".": Cells(i, k) = InStr(iv, ".") + 6
End If 」
このコードで4000回したところ 2.81秒でした。
桁数に誤りはありません。(0やマイナスの数値の場合も)
対象数値0の場合、0桁とするのか、1桁とするのか気になります。
 (上記のコードは1桁に扱います)
小数点以下の桁数はカウントしません。
対象数値がマイナスの場合、上記のコードは、マイナス記号を除いて桁数を計算しています。

Int(Log(対象数値) / i10)の場合、対象の数値が
0.002の場合、桁数はマイナスになります。(logですから)
0の場合、Log(0)はエラーになります。
マイナスの場合も、Log(マイナス)はエラーになります。
この回答への補足あり
    • good
    • 0

この質問は、整数の桁数を求めるために


dd = Int(Log(ses) / Log(10) )+1
という計算を行い、正しい結果が得られるという前提で質問されていますが、
VBAでは正しい結果は得られません。
1,10,100,...1000000000について上記の式で正しい結果が得られるかどうか
確認したところ、1000,100000等で誤りが発生しています。
そもそも、質問者様が本当になさりたいことは、
「手っ取り早く、かつ正確に、最短時間で整数の桁数を求めたい。」
ということかと思いますが、残念ながら、上記はトレードオフの関係にあり、
それらの全てを満足することは出来ません。
とりあえず、手っ取り早く、正確な方法の1案として、
「整数を文字列に変換し、その桁数を求める」方法があります。
処理速度の観点から、もっと速い方法を望まれる場合は、VBAではありませんが
http://smdn.jp/programming/netfx/tips/get_number …
にいくつかの方法が示されていますので参考にして下さい。

以下は、Int(Log(ses) / Log(10) )+1 方式と「整数を文字列に変換し、その桁数を求める」方式
を呼び出してその結果を表示したものです。
----------------------------------------------
Public Sub testlog()
Dim vals() As Long
Dim vals2() As Long
Dim i, j As Long
vals = MyLongArray(1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000)
For i = 0 To UBound(vals)
j = ketasu(vals(i))
Debug.Print "LOG 方式 val=" & CStr(vals(i)) & " keta=" & CStr(j)
Next
For i = 0 To UBound(vals)
j = ketasu2(vals(i))
Debug.Print "文字列方式val=" & CStr(vals(i)) & " keta=" & CStr(j)
Next
vals2 = MyLongArray(1, 9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, 2147483647)
For i = 0 To UBound(vals2)
j = ketasu(vals2(i))
Debug.Print "LOG 方式 val=" & CStr(vals2(i)) & " keta=" & CStr(j)
Next
For i = 0 To UBound(vals2)
j = ketasu2(vals2(i))
Debug.Print "文字列方式val=" & CStr(vals2(i)) & " keta=" & CStr(j)
Next

End Sub


Public Function ketasu(num As Long) As Long
ketasu = Int(Log(num) / Log(10)) + 1
End Function
Public Function ketasu2(num As Long) As Long
ketasu2 = Len(CStr(num))
End Function

Public Function MyLongArray(ParamArray arrs()) As Long()
Dim i As Long
Dim vals() As Long
For i = 0 To UBound(arrs)
ReDim Preserve vals(i)
vals(i) = CLng(arrs(i))
Next
MyLongArray = vals
End Function


-----------------------------------------------------
実行結果は以下の通り
LOG 方式 val=1 keta=1
LOG 方式 val=10 keta=2
LOG 方式 val=100 keta=3
LOG 方式 val=1000 keta=3
LOG 方式 val=10000 keta=5
LOG 方式 val=100000 keta=5
LOG 方式 val=1000000 keta=6
LOG 方式 val=10000000 keta=7
LOG 方式 val=100000000 keta=9
LOG 方式 val=1000000000 keta=9
文字列方式val=1 keta=1
文字列方式val=10 keta=2
文字列方式val=100 keta=3
文字列方式val=1000 keta=4
文字列方式val=10000 keta=5
文字列方式val=100000 keta=6
文字列方式val=1000000 keta=7
文字列方式val=10000000 keta=8
文字列方式val=100000000 keta=9
文字列方式val=1000000000 keta=10
LOG 方式 val=1 keta=1
LOG 方式 val=9 keta=1
LOG 方式 val=99 keta=2
LOG 方式 val=999 keta=3
LOG 方式 val=9999 keta=4
LOG 方式 val=99999 keta=5
LOG 方式 val=999999 keta=6
LOG 方式 val=9999999 keta=7
LOG 方式 val=99999999 keta=8
LOG 方式 val=999999999 keta=9
LOG 方式 val=2147483647 keta=10
文字列方式val=1 keta=1
文字列方式val=9 keta=1
文字列方式val=99 keta=2
文字列方式val=999 keta=3
文字列方式val=9999 keta=4
文字列方式val=99999 keta=5
文字列方式val=999999 keta=6
文字列方式val=9999999 keta=7
文字列方式val=99999999 keta=8
文字列方式val=999999999 keta=9
文字列方式val=2147483647 keta=10
    • good
    • 0

#4さんへ



>#3 の方法を使うと 2.30258509299404 になってしまい, log(10) はこの数値とも (Double 以上では) 異なるのでやっぱり微妙な値のときに困ります.

こちらに、あまり、つまらないことを振らないください。

この問題は、暗号解読のために、精度を上げるなんていうわけではありません。

>num = Int(Log(10#) * 10 ^ 14) / 10 ^ 14
私自身、桁を求める数式に、上記のような数値を代入することはまずありえません。

しかし、これは以前も、同じような議論をしたことがあります。これが、15でも、16でも、また、浮動小数点型を、Decimal型にしようが、そんなことは関係ないのです。ある程度の経験者なら、これは有効桁数はいくつか、直感的に分かるはずです。それを桁数、増やしたら大丈夫とかいう理屈は成り立ちません。

求めるものが整数なら、小数点の下桁を増やすことで解決するなら、どこか矛盾や見落としが存在していると考えるのがふつうだと思います。

この問題は、どのみち、無理数は無理数なので、それを最後の桁に2桁加えようが加えまいが、いずれ丸めが存在しうるのです。それを、分母に代入するのですから、「逆数」になるわけです。この場合、代入値が間違いかそうでないか、どちらかしかありません。求める数は、結果として整数値ですから、微妙も何もないのです。

それ以外に求めるものでしたら、また別問題です。それは、ここの質問のトピックではありません。倍精度型で不十分な場合は、VBAやVB6では、Double型の数値の下桁を増やしたところで、何の意味もありません。

むろん、この元の数値は、Logを使う以上は、その代入値は絶対値(abs)にしなくてはなりませんが、今回、ご質問者さんはお分かりだと思いますから、それは割愛します。
    • good
    • 0
この回答へのお礼

皆さん回答ありがとうございます。

2.30258509299405というのはもちろんVBAで得られたものです。

Dim www As Double
www = Log(10)
で得られます。

仰るとおり、電卓ソフトを使うと更に長い桁が表示されます。
つまり電卓ソフトでは四倍精度が使われているのではないでしょうか?

それで質問内容が伝わってないのですが
VBAでは変数では4倍精度を使用することができませんが
メモリー上では4倍精度で計算が行われているということでしょうか?

それとも、10進数と2進数の違いで
同じ倍精度であっても有効数字が異なるということなのでしょうか?

お礼日時:2015/11/14 13:03

まず, Windows7 の電卓を使って log(10) を計算すると


2.30258509299404568...
という数値が出ます. で, この数値と質問文にある 2.30258509299405 とは (Double 以上の範囲を持つ数値型では) 異なります.

ちなみに #3 の方法を使うと 2.30258509299404 になってしまい, log(10) はこの数値とも (Double 以上では) 異なるのでやっぱり微妙な値のときに困ります.

ということで, #1 で言われているように
log(10) の値をリテラルで書かずに計算させる
というのが安全でしょう.

謎な理由によりどうしてもリテラルで書く必要があるなら, あと 2桁追加してください.
    • good
    • 0

こんばんは。


>dd = Log(ses) / 2.30258509299405

このリテラル値が違っていませんか?最後の「5」は、丸めが発生しているだけで、「5」ではないはずです。
こういうリテラル値は危ないので、

num = Int(Log(10#) * 10 ^ 14) / 10 ^ 14
dd = Int(Log(ses) / num) + 1

としてみたらいかがでしょうか。

なお、14桁の必要もありませんが、質問のリテラル値に一番近い数字にするために14桁にしました。
むろん、そのまま、加工しないで、num =Log(10#) にする方がいいに決まっています。それによってスピードがどうとかは分かりませんが。

Int(Log(ses) / Log(10#)) + 1

この数式そのものは、桁数を取る数式でしょうけれども、最初の部分で、Int を使っているのですから、全体の結果の誤差ではなくて、代入値そのもののミスだけだと思います。

それ以上の説明は必要ないと思います。
    • good
    • 0

浮動小数点数全般について


https://ja.wikipedia.org/wiki/%E6%B5%AE%E5%8B%95 …
特に(VBA等で使われていると思われる)IEEE754という規格について
https://ja.wikipedia.org/wiki/IEEE_754

2.30258509299405 は電卓使うとか、 log(10)を何かの方法で表示させたものかと思います
この 2.30258509299405 は、上記の記事にあるような a * 2^n を10進法で「近似」して、適当な桁で四捨五入したものです。
そんな近似や丸め処理がされた分、log(10)の戻り値である a * 2^n 形式とは違う値になっています。


別の環境ですが、計算して、限界まで桁を増やしてみたところ
405
ではなく
4046...
と続きました。

つまり
【Int(Log(100) / log(10) )+1】

【Int(Log(100) / 2.302585092994046... )+1】
で計算しています。このわずかな差が、結果の違いになっています。
    • good
    • 1

if log(10)=2.30258509299405 then


msgbox("同じ")
else
msgbox("違う")
end if

これを実行したら「違う」と表示されませんか?
一見同じ、と「同じ」とは違います。
いろんな関係で、浮動小数点数と十進数との間には、誤差があります。
この誤差が、log(100)/ log10 が3未満と3以上に分けています。

そもそも、log10は無理数ですから、有限の桁で表現すれば、必ず誤差が出ます。

l10 = log(10) などと変数に入れておくことで、直接書いた 2.30...と log(10)との差は埋めることはできます。
ですが、 真のlogと、コンピュータ乗で打ち切ったlogとの誤差は防ぎようがありません。
    • good
    • 1
この回答へのお礼

log10は無理数であることはもちろん分かっているのですが
メモリー上であっても無理数を扱うことはできないので
倍精度などの型式で保存されているのではないのでしょうか?

そもそもlog(10)と式を入力した時点で
何か仮想の変数に値が代入された後で
log(100)と割り算が行われていたと思っていたのですが
違うということでしょうか?

この辺りが詳しく教えていただけないでしょうか?
あるいは解説ページがあれば示していたけないでしょうか?

お礼日時:2015/11/13 23:00

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!


このQ&Aを見た人がよく見るQ&A