マクロ変数singleで小数点計算が狂う理由とは?

このQ&Aのポイント
  • Windows8.1エクセル2010でマクロを使って小数点の計算をする際、マクロ変数singleを使用すると計算結果が正確でなくなる問題が発生します。
  • 具体的には、足される数と足す数をsingle型の変数として宣言し、計算結果をsingle型の変数に代入すると、本来の計算結果と異なる値が出力されます。
  • この問題を解決するためには、double型の変数を使用することで正確な計算結果を得ることができます。single型の仕組みは有効桁数が限られており、丸め誤差が生じる可能性があるため、注意が必要です。
回答を見る
  • ベストアンサー

マクロ変数 single で小数点計算が狂います

Windows8.1 エクセル2010です。 マクロを使っており、小数点の計算をしたいため、初めてsingle double の変数を使いました。 例として、 Sub シングル() Dim 足される As Single Dim 足す As Single Dim 合計 As Single 足される = Range("a1").Value 足す = Range("b1").Value 合計 = 足される + 足す Range("c1").Select ActiveCell.FormulaR1C1 = 合計 End Sub A1=2.1 B1=5.1 普通は7.2となるのですが、7.19999980926513 と出ました。 doubleに変数を切り替えたら、ちゃんと7.2と出ました。 singleの仕組みがよくわかりません。 有効桁数数は「7桁」なら、小数点第7位まで扱える…という単純なものではないのでしょうか?

noname#248169
noname#248169

質問者が選んだベストアンサー

  • ベストアンサー
  • imogasi
  • ベストアンサー率27% (4737/17068)
回答No.2

参考までに http://dobon.net/vb/dotnet/beginner/floatingpointerror.html などを読んでおくとよい。 >SingleやDoubleのような浮動小数点数型は、値を2進数で格納しています。しかし、ほとんどの10進数の小数は2進数で表現することができません。そのためこのような値はSingleやDouble型では近似値でしか表現することができず、その誤差が上記のような非常識的な計算結果として現れます。 ーー ・浮動小数点数型に変換する(言語内部で)ときの問題点(本件) ・浮動小数点数型を使いだしたら、繰り返し演算を重ねたりすると、途端にむつかしい問題に直面する。(さらに関連した問題点) ーー 参考 その他目についた記事 http://qa.atmarkit.co.jp/q/9838 http://dangerous-animal141.hatenablog.com/entry/2014/05/10/000000

noname#248169
質問者

お礼

http://dobon.net/vb/dotnet/beginner/floatingpointerror.html とてもわかりやすいページでした。 あと、固定小数点数と浮動小数点数で、仕組みがわかりました。 ありがとおうございました。

その他の回答 (5)

  • titokani
  • ベストアンサー率19% (341/1726)
回答No.6

>有効桁数数は「7桁」なら、小数点第7位まで扱える…という単純なものではないのでしょうか? はい、みなさん書かれている通り、単純ではないです。 単純に使いたいのなら、Decimal型を使うのかいいと思います。 原理的にはSingle やDoubleより遅いはずですが、しょせんマクロですから、気にする程度ではないと思います。

noname#248169
質問者

お礼

ありがとうございました。 decimalという関数は初めてですね。 本を読みましたが、載ってない…。 HPで調べて使ってみます。

回答No.5

そのちゃんと出た7.2も実は怪しい。 イミディエイトウィンドウ(VBEの方で Ctrl + G)を表示し ?2.1+5.1-7.2 でEnterしてみてください。 0 になってほしいのですが、-4.44089209850063E-16 と指数表示されます。 ?2.1+5.1=7.2 では False(違う)と返ってきます。 WorkSheetでも、値が丸められて表示されるのでさらに困惑することになります。。。 ここを読んでもらうと少しスッキリするかもしれません。 第4回 演算誤差の正体 http://pc.nikkeibp.co.jp/pc21/special/gosa/eg4.shtml 1~4まであるので時間のある時にでも http://pc.nikkeibp.co.jp/pc21/special/gosa/eg4.shtml 蛇足ですが、 WorkSheet関数のRoundとVBAのRound関数の違い。 WorkSheetの方は =round(1.5,0) → 2 =round(2.5,0) → 3 これは納得できるかと思いますが、VBAのRound関数では ?round(1.5,0) → 2 ?round(2.5,0) → 2 ?round(3.5,0) → 4 銀行型丸め(偶数)になります。 他にも・・・ 興味があれば 丸めを行うカスタム プロシージャを実装する方法 https://support.microsoft.com/ja-jp/kb/196652

noname#248169
質問者

お礼

皆様の回答により、仕組みはわかりました。ですが、新たな疑問が沸いてしまいましたのです。 その時、頂きましたHP >第4回 演算誤差の正体 「固定小数点の方がわかりやすいですよね。どうして浮動小数点を使うんですか?」 同じ質問を、ハルがしてましたが、エリカの回答で、スッキリです。 ありがとうございました。

  • chie65535
  • ベストアンサー率43% (8516/19360)
回答No.4

追記と訂正。 Windows7以降のバージョンのWindowsに付属の電卓で 4 2 1 0.125 0.0625 0.0078125 0.00390625 0.00048828125 0.000244140625 0.000030517578125 0.0000152587890625 0.0000019073486328125 0.00000095367431640625 を全部足すと、EXCELとは異なり 7.19999980926513671875 という答えを表示し 7.19999980926513 になりません。 これは「Windowsの電卓が、四則演算に限り、独自の精度で演算をしているから」で、EXCELなどの「IEEE浮動小数点数」とは異なる結果になります。

noname#248169
質問者

お礼

ありがとうございました。 最初は、変な小数点で ? マークがつきましたが、理由がわかって良かったです。 何度も回答頂きありがとうございました。

  • chie65535
  • ベストアンサー率43% (8516/19360)
回答No.3

Singleは「単精度浮動小数点数」と言います。 Doubleは「倍精度浮動小数点数」と言います。 IEEE単精度では、データ構造は 符号1ビット+指数部8ビット+仮数部23ビット になっています。倍精度では 符号1ビット+指数部11ビット+仮数部52ビット になっています。 で、実際にどのように数値を記憶しているかと言うと「2進数で記憶」しています。 例えば「7.5」なら「2の2乗+2の1乗+2の0乗+2の-1乗」つまり「4+2+1+0.5」のように記憶しています。 では、2.1は、と言うと 2の1乗+2の-4乗+2の-5乗+2の-8乗+2の-9乗+2の-12乗+2の-13乗… つまり 2+0.0625+0.03125+0.00390625+0.001923125+0.000244140625+0.0001220703125+... のようになります。 2進数で書くと 10.0001100110011001100110011001100110011001100110011... です。 この数値を単精度で記録すると、仮数部は23ビットしかありませんから 10.0001100110011001100110 までしか記憶できません。 5.1も同様に 101.000110011001100110011 までしか記憶できません。 この2つを足し算した値も 111.001100110011001100110 までしか記憶できません。 ここで、この「単精度の値」を「倍精度で記憶している、セルに代入する」と、どうなるでしょうか? 倍精度は「仮数部52ビット」ですから「単精度の値を倍精度に代入すると、29ビット分、足りない」ですから「余った29ビット分」には「0」が足されます。 なので、セルには、2進数で 111.00110011001100110011000000000000000000000000000000 という値が代入されます。この値は、倍精度では 7.19999980926513 という値になります。 倍精度では、7.2は、2進数で 111.00110011001100110011001100110011001100110011001100 という値になりますから 111.00110011001100110011000000000000000000000000000000 という値は「7.2より、ちょっと小さい値」になってしまいます。 試しに、電卓で 4 2 1 0.125 0.0625 0.0078125 0.00390625 0.00048828125 0.000244140625 0.000030517578125 0.0000152587890625 0.0000019073486328125 0.00000095367431640625 を足してみて下さい。「7.19999980926513」になり、7.2にはなりません。 「変な値になる理由(7.2にはならない理由)」は「単精度を倍精度に変換した時に、足りない29ビット分が、0で埋められたから」です。 このような「精度が異なる物に変換して代入した時の誤差」を防ぐには「一旦、文字列に変換してから、数値に変換する」などの工夫が必要です。 例えば ActiveCell.FormulaR1C1 = 合計 の行を ActiveCell.FormulaR1C1 = Val(Str(合計)) に変えてみて下さい。C1セルに、ちゃんと「7.2」が代入されます。

  • f272
  • ベストアンサー率46% (7998/17100)
回答No.1

7.19999980926513の8桁目(小数点以下8桁ではなく,一番大きな桁から数えて8桁目)を四捨五入するとちゃんと7.2になっているよね。有効桁数が7桁と言うのはそういうことです。

noname#248169
質問者

お礼

確かに、四捨五入すると 7.2ですね。 ありがとうございました。

関連するQ&A

  • 小数点第2位の数値の計算

    下記のような処理があるとします。 Dim 変数1 As 宣言 Dim 変数2 As 宣言 Dim 変数3 As 宣言 Dim 変数4 As 宣言 変数1 = 0 変数2 = 0 変数3 = 0 変数4 = 0 変数1 = テキストボックス1.Text 変数2 = ラベル2.Caption 変数3 = ラベル3.Caption 変数4 = (変数2 + 変数3) - 変数1 ラベル4.Caption = 変数4 変数1~3には、小数点第2位までの 数値が入る事があるのですが、 例) 変数1 = 3.0 変数2 = 4.75 変数3 = 7.75 の場合、変数4 = 0 と計算される。 その場合、変数4の計算結果が 正しく出力されません。 宣言はDoubleを使うと計算結果は 16進数表示になり、Singleを使うと0になります。 正しい計算結果を出すには どのようにすれば良いでしょうか?

  • 単純な掛け算なのにわけのわからない小数点が、、、

    表題のとおりですが、EXCEL VBAでシートと複合させて計算したところ次のような結果が出ました。 マクロは以下のとおりです。 Private Sub CommandButton1_Click()  Dim i As Integer Dim n As Integer Dim x As Integer Dim y As Single  Dim d1 As Single Dim d2 As Single  n = Range("L4").Value 'L4には現在399が入力されています。 y = Range("L2").Value 'L2には現在0.048が入力されています。  d1 = Range("D11").Value d2 = Range("E11").Value If n = 0 Then Exit Sub For i = 0 To n x = i + 6 Cells(x, 13) = i Cells(x, 14) = Cells(x, 13) * y Cells(x, 15) = Cells(x, 14) + d1 Cells(x, 16) = d2 - Cells(x, 14)     x = x + 1 Next i End Sub こうするとCells(7, 14)に0.0480000004172325という数字が入り始め、 続きも同じように小数点の小さい桁にわけの分からない数字が出て来ます。いったい何が原因か分かりません。 よろしくお願いします。

  • Excelマクロ:変数でセル範囲指定

    マクロの迷い人です。 Excelの表をマクロで印刷しようと思っています。 行の数が毎回違うため、最終セルもその都度指定しなければなりません。 A1 B1 A2 B2 A3 B3 A4 B4 この例で、A5 B5 以降は空セルとします。 印刷範囲を Range("A1:B4")と書かずに、そのときどきのアクティブセルを変数に代入し、変数を使って範囲指定したいのです。 Sub MacroTest () Dim a As Variant Dim b As Variant Range("B1").Activate Do While a <> 0 ActiveCell.Offset(1, 0).Activate '空白でなければ一つ下に移る a = ActiveCell.Value Loop ActiveCell.Offset(-1, 0).Activate '上の行に移る b = ActiveCell.Value Range("A1:"& b).Select End Sub こうしてみましたがダメでした。 デバッグの方法がわからないので教えて下さい。よろしくお願いします。

  • より単純なマクロにしたいのですが・・・

    Dim Ws1 As Worksheet, Ws2 As Worksheet Dim i As Long Set Ws1 = Worksheets("名簿") Set Ws2 = Worksheets("表面") For i = Ws1.Range("B2").Value To Ws1.Range("B4").Value Ws2.Range("HA2").Value = i Ws2.Select If Range("HD2").Value = 1 Then Range("HG2").Select ActiveCell.FormulaR1C1 = "1" Range("HD2").Value = 2 Then Range("HG2").Select ActiveCell.FormulaR1C1 = "1" Sheets(Array("表面", "裏面")).Select ExecuteExcel4Macro "PRINT(1,,,1,,,,,,,,2,,,TRUE,,FALSE)" Range("HG2").Select ActiveCell.FormulaR1C1 = "2" ElseIf Range("HD2").Value = 3 Then Range("HG2").Select ActiveCell.FormulaR1C1 = "1" Sheets(Array("表面", "裏面")).Select ExecuteExcel4Macro "PRINT(1,,,1,,,,,,,,2,,,TRUE,,FALSE)" Range("HG2").Select ActiveCell.FormulaR1C1 = "2" Sheets(Array("表面, "裏面")).Select ExecuteExcel4Macro "PRINT(1,,,1,,,,,,,,2,,,TRUE,,FALSE)" Range("HG2").Select ActiveCell.FormulaR1C1 = "3" End If Sheets(Array("表面", "裏面")).Select ExecuteExcel4Macro "PRINT(1,,,1,,,,,,,,2,,,TRUE,,FALSE)" Next Sheets("名簿").Select Range("F1").Select Range("C5").Select Selection.End(xlDown).Select ActiveCell.Offset(1, 0).Range("A1").Select End Sub "表面"シートのHA2に任意の印刷No.が入っていき、"表面"シートのHD2にそれぞれ任意の印刷No.に応じて1~3の数字が振られているためその割り振り数字が入っていきます。HD2の数字が1の時は、HG2セルに1の数字が順に入り印刷(表面と裏面は印刷設定で両面になっています)し、HD2の数字が2の時は、HGセルに1の数字が入り印刷、次にHGセルに2の数字が入り印刷。HD2の数字が3の時は、HGセルに1の数字が入り印刷、次にHGセルに2の数字が入り印刷、次にHGセルに3の数字は入り印刷というマクロになっています。実際に動かしてみると 非常に重いため、よりスマートにできるようなコードを考えているのですがこれが私の限界です。どこでも良いので、簡潔にできる所があればアドバイスをいただけると幸いです。

  • VBAで計算した結果がデータ型により相違してしまいます。VBAに詳しい

    VBAで計算した結果がデータ型により相違してしまいます。VBAに詳しい方、理由を教えてください。よろしくお願いします。 Dim 税率 As Single Dim 売上 As Double 単価=Range("b5").Value:個数=Range("d5").value:税率=Range("f5").Value 売上=(1+税率)*単価*個数 (1)単価=30000、個数=15、税率=0.05と入力すると売上の数値=472500.000335276となります。 (2)売上のデータ型をsingleにした場合は、売上=472500となります。 (3)売上のデータ型Single、税率のデータ型をDoubleとした場合も売上=472500となります。 (1)の結果の小数点以下の数値の意味がわかりません。 よろしくお願いいたします。

  • VBA 小数点の割り算

    お世話になります。 以下のように計算をしたいのですが。 Dim A_DATE As Single Dim B_DATA AS Single Dim C_DATA AS Single A_DATA = 69.63 B_DATA = 1651.8 C_DATA = A_DATA / B_DATA とすると C_DATAには希望は0.04214・・・・ がセットされてほしいのですが、 4.2154・・・となってしまうのです。 変数の指定の仕方がおかしいのか、 割り算の仕方がおかしいのかよく分かりません。 よろしくお願いします。

  • VB6.0での小数点の扱いについて

    現在、VB6.0を使用しており、小数点の扱いに困っています。 Sub Keisan() Dim A As String Dim B As String Dim C As String A = 1.29033 B = 1.91458 C = CStr(A + CDec((B - A) / 6) * 3) MsgBox C End Sub 上記のプログラムを実行すると、 「1.602455000000001」と表示されますが、 電卓を用いて計算すると、 「1.602454998・・・」となり、微妙に誤差が出てしまいます。 小数点を整数にして計算→元の桁数に戻す、という 処理を行うと、誤差なく求めることが出来ましたが、 「もっとスマートなコードにして」と言われてしまいまして どうしたものかと思っております。 この誤差を解決する方法は無いでしょうか?

  • マクロの編集方法を教えて下さい。

    自分で記録したマクロを親切な方に編集してもらいました。実行スピードが格段に速くなったのですが、さらに処理したい項目が出来たので、別に記録してコピー、適切な箇所に挿入したのですが、実行時エラーが出ます。どう直していいのか分かりません。分かる方教えて下さい。 Sub Incert12() Dim wRow As Long Dim i As Integer Dim tbl(1 To 12, 1 To 1) As Integer wRow = Range("A65536").End(xlUp).Row Rows(CStr(wRow) & ":" & CStr(wRow + 11)).Insert Range(Cells(wRow + 1, "B"), Cells(wRow + 11, "B")).FormulaR1C1 = "=R[-1]C" '↑(1)これの代わりにB列を12行全て結合したい For i = 1 To 12 tbl(i, 1) = i Next i Range(Cells(wRow, "C"), Cells(wRow + 11, "C")).Value = tbl '↑(2)これに加えてA列に以下の処理も加えたい 'ActiveCell.Offset(-2, -8).Range("A1").Select 'ActiveCell.FormulaR1C1 = "=R[-1]C+1" 'ActiveCell.Select 'Selection.AutoFill Destination:=ActiveCell.Range("A1:A12"), Type:= _ ' xlFillDefault 'ActiveCell.Range("A1:A12").Select '↓(3)F列ではなく、FからK列までにしたい。 Cells(wRow + 12, "F").AutoFill Range(Cells(wRow, "F"), Cells(wRow + 12, "F")) 'これが私が作ったマクロ。(2行目に問題ありとの事) 'ActiveCell.Offset(-1, 5).Range("A1:F1").Select 'Selection.AutoFill Destination:=ActiveCell.Range("A1:F13"), Type:= _ ’ xlFillDefault 'ActiveCell.Range("A1:F13").Select Cells(wRow, 1).Select End Sub 以上(1)~(3)を直したいのです。どなたかよろしくお願い致します。

  • マクロで時間の合計・平均計算

    いつもお世話になっています。 Excel2000でセルに表示された時間の平均を計算するマクロを作っています。 セルB4・B5・B6にはそれぞれ"00:00:01"・"00:32:54"・"02:33:12"が表示されています。 セルの書式設定はユーザ定義型で"hh:mm:ss"です。 B4からB6の合計時間を求めて、B7(書式設定は標準)に表示させるまでは    Worksheets("Sheet1").Select       Range("B7").Select       ActiveCell.FormulaR1C1 = "=sum(R[-3]C:R[-1]C)" で"03:06:07"と表示できたのですが、"=average(R[-3]C:R[-1]C)"とすると "09:02:02"と合計でも平均でもない値が表示されてしまいました。 "=sum(R[-3]C:R[-1]C)/3"とすると"0.3764"と小数で表示されてしまいます。 それならばと、上記述の下に"MyTime=ActiveCell.Value"や"MyTime=Range("B7").Value"と記述してみたのですが、小数で値が入ってきてしまいます。 "03:06:07"さえ取れれば、あとは文字列を数値に変換して無理やり計算しようと 思っているのですが、それすらできず、困っています。 割り切れない秒数は切り上げにするとして、 B4からB6の時間の平均"01:02:03"を出すにはどうしたらよいのでしょう? どなたかご存知の方、教えてください!!

  • 変数の使い方の注意点なんですが・・・?

    Windows XP Home Edition Excel 2002 当方は、まだ基本がしっかり頭に入っていないので、 勝手な疑問が出てきているようでございます。 まだまだ勉強中ですが、 下記コードの場合、動作はしますが、 変数 c の使い方は間違っているのではないですか? 4は、 同プロシージャ内なので、 変数 c と r というように、区別して記述しないといけないのではないかと思って いるのですが。 3は、 プロシージャが違うので問題はないと思っております。 Dim r As Range, s As Range と記載してもいいのでしょうか?   つまり、曖昧に、ごっちゃ混ぜにしてしまうと、エラー等、PCに支障をきたすことになって しまうのではないかと心配でございます。 また、こんな記述だと、エラー等、PCに支障をきたすことになって しまうという例を 1つか2つ(又はHP等) 教えて頂けると有り難いです。 初歩的な例で結構です。 全くの初歩的な(ざっくりな)お答えで結構ですのでよろしくお願い致します。 Sub てす1() Dim r As Range, s As Range   Set s = Cells(1, 1)   s.Select  MsgBox " A1 です " End Sub '------------------------ Sub てす2() Dim r As Range, s As Range   Set s = Cells(1, 2)   s.Select  MsgBox " A2 です " End Sub '------------------------ Sub てす3() Dim r As Range, s As Range   Call てす1   Call てす2  MsgBox "  A1とA2 です " End Sub '------------------------ Sub てす4() Dim r As Range, s As Range   Set s = Cells(1, 1)   s.Select  MsgBox " A1 です "   Set s = Cells(1, 2)   s.Select  MsgBox " A2 です "  MsgBox " A1とA2 です " End Sub

専門家に質問してみよう