none
Double型の丸め RRS feed

  • 質問

  • Double型の数値をn桁に丸める(四捨五入)のはどうすればいいのでしょうか。
    例えば1.234567890123456E20を有効8桁に丸める。
    1.2345679E20というようにする。

    よろしくお願いいたします。

    2008年12月10日 1:59

回答

  • 外池と申します。

     

    丸める(四捨五入)操作が「本当に」必要なことなのかどうかは、すでに良く検討されている・・・、と思いますが、まぁ、念のために注意喚起しておきます。

     

    で、おそらく、最後に結果を表示する段階で、有効桁数を指定して、かつ、「四捨五入」で丸めた表示にしたいんだと、推察しています。そんな場合、私なら、

    • Log10関数を使って指数部を得て、元の数値を割ってやります。これで、仮数部と指数部が分けられます。
    • その上で、仮数部に対して10のn乗を掛けてやって、四捨五入したい桁を小数第1位にもってきます。
    • あとは、0.5足してFloor関数でキリ飛ばします。(厳密なことを言えば、あとは文字列として処理した方が良い。)
      補足:Math.Round(Double, Int32, MidpointRounding)でもいけそうですね。MidpointRounding.AwayFromZeroを指定すればよい。
    • 戻す方向として、10のn乗で割ってやる。(しかし、ここでまた変な丸め誤差が入り得ることを考えると面倒すぎる。)

    くどいよいですが、もし、計算過程の途中で四捨五入を考えておられるなら、あまり意味のないことをやっていると思いますのでご注意を。Decimal型を使った方が良いと思います。

     

     

     

    2008年12月10日 2:19
  • 計算途中で四捨五入するのではなくて、結果を小数点 x 位で四捨五入して
    表示したいということではないのですか? 

     

    そうであれば、フォーマットで指定できますが。例えば以下のように。

     

    double d = 1.234567890123456789E+20;
    Console.WriteLine(String.Format("d = {0}", d));
    Console.WriteLine(String.Format("d = {0:E}", d));
    Console.WriteLine(String.Format("d = {0:E1}", d));
    Console.WriteLine(String.Format("d = {0:E2}", d));
    Console.WriteLine(String.Format("d = {0:E3}", d));
    Console.WriteLine(String.Format("d = {0:E4}", d));
    Console.WriteLine(String.Format("d = {0:E5}", d));
    Console.WriteLine(String.Format("d = {0:E6}", d));
    Console.WriteLine(String.Format("d = {0:E7}", d));
    Console.WriteLine(String.Format("d = {0:E8}", d));

     

    結果は:

     

    d = 1.23456789012346E+20
    d = 1.234568E+020
    d = 1.2E+020
    d = 1.23E+020
    d = 1.235E+020
    d = 1.2346E+020
    d = 1.23457E+020
    d = 1.234568E+020
    d = 1.2345679E+020
    d = 1.23456789E+020

     

    0.5 足して小数点以下を切り捨てという操作は、丸めの誤差でうまくいかな
    い場合があります。

    2008年12月10日 13:17

すべての返信

  • 外池と申します。

     

    丸める(四捨五入)操作が「本当に」必要なことなのかどうかは、すでに良く検討されている・・・、と思いますが、まぁ、念のために注意喚起しておきます。

     

    で、おそらく、最後に結果を表示する段階で、有効桁数を指定して、かつ、「四捨五入」で丸めた表示にしたいんだと、推察しています。そんな場合、私なら、

    • Log10関数を使って指数部を得て、元の数値を割ってやります。これで、仮数部と指数部が分けられます。
    • その上で、仮数部に対して10のn乗を掛けてやって、四捨五入したい桁を小数第1位にもってきます。
    • あとは、0.5足してFloor関数でキリ飛ばします。(厳密なことを言えば、あとは文字列として処理した方が良い。)
      補足:Math.Round(Double, Int32, MidpointRounding)でもいけそうですね。MidpointRounding.AwayFromZeroを指定すればよい。
    • 戻す方向として、10のn乗で割ってやる。(しかし、ここでまた変な丸め誤差が入り得ることを考えると面倒すぎる。)

    くどいよいですが、もし、計算過程の途中で四捨五入を考えておられるなら、あまり意味のないことをやっていると思いますのでご注意を。Decimal型を使った方が良いと思います。

     

     

     

    2008年12月10日 2:19
  • 計算途中で四捨五入するのではなくて、結果を小数点 x 位で四捨五入して
    表示したいということではないのですか? 

     

    そうであれば、フォーマットで指定できますが。例えば以下のように。

     

    double d = 1.234567890123456789E+20;
    Console.WriteLine(String.Format("d = {0}", d));
    Console.WriteLine(String.Format("d = {0:E}", d));
    Console.WriteLine(String.Format("d = {0:E1}", d));
    Console.WriteLine(String.Format("d = {0:E2}", d));
    Console.WriteLine(String.Format("d = {0:E3}", d));
    Console.WriteLine(String.Format("d = {0:E4}", d));
    Console.WriteLine(String.Format("d = {0:E5}", d));
    Console.WriteLine(String.Format("d = {0:E6}", d));
    Console.WriteLine(String.Format("d = {0:E7}", d));
    Console.WriteLine(String.Format("d = {0:E8}", d));

     

    結果は:

     

    d = 1.23456789012346E+20
    d = 1.234568E+020
    d = 1.2E+020
    d = 1.23E+020
    d = 1.235E+020
    d = 1.2346E+020
    d = 1.23457E+020
    d = 1.234568E+020
    d = 1.2345679E+020
    d = 1.23456789E+020

     

    0.5 足して小数点以下を切り捨てという操作は、丸めの誤差でうまくいかな
    い場合があります。

    2008年12月10日 13:17
  • 外池です。

     

    そう・・・、私も、「0.5足す」のところは、Double型だとダメなときあり、というのはわかってるんですが・・・。Decimalなら大丈夫そうですけどね。

     

    で、SurferOnWWWさんの仰る書式指定文字を使う方法ですが、ドキュメントを見ても「四捨五入」ということが明示されていないくて、結構不安だったりします。どこかに記載あります? ToEvenな丸めにはなりませんか?

     

    あぁ・・・、自分でやってみましたが、大丈夫そうですね。"E2"を指定した際には、1235.0は、1.24E+3になるし、1245.0は1.25E+3になりますね。そうすると、指数表示じゃないときも、四捨五入になってるんですかね?

     

     

     

     

     

    2008年12月10日 14:15
  • 外池です。他のところでもこの話題でえらく盛り上がったことがあるみたいですね。

    http://bbs.wankuma.com/index.cgi?mode=al2&namber=24424&page=20&KLOG=46

    これを見る限り・・・、ドキュメント類で確証を得るのは難しいようです。

    経験則で、書式指定文字で指定した桁数へ、四捨五入で丸められる・・・、という感じでしょうか。

     

    ただ、少し考えてみたのですが、私が申し上げた「0.5を足して・・・」の方法ですが、元の数値をちゃんとシフトして、丸めたい桁を小数第1位にもってきた上で0.5を足す方法をとれば、この時点では正確に四捨五入できると思うのですが。0.5は二進数で正確に表現できるゆえ。

     

    間違っていたらご指摘ください。

     

    ただし、この後、逆にシフトさせたら、その結果には再度誤差が入り込んでしまうことは理解しています。

     

     

    2008年12月10日 14:35
  •  

    外池 さん、SurferOnWww さん、回答ありがとうございます。

    おっしゃるとおり、結果を四捨五入して表示したいと言う事です。
    とりあえずFormat関数を使ってやりたいと思います。
    Format("d = {0:E3}", d)
    こういう使い方があるのは知りませんでした。
    それ以外にも貴重な情報を頂きありがとうございました。

    2008年12月11日 3:20
  •  外池 さんからの引用

    経験則で、書式指定文字で指定した桁数へ、四捨五入で丸められる・・・、という感じでしょうか。

     

    お察しの通り経験則(FORTRAN の昔から四捨五入で丸められるし、Turbo C

    でもそうだったし、.NET で実際に試してもそうなる等々)で、規格を調べて確
    証を得ているわけではないです。

     

    とりあえず自分的には経験則で十分なので、規格を読んで調べるまでもない・・
    ・というより、どの規格に書いてあるのかさえ分からないと言ったほうが当たっ
    てます。FORTRAN なら ANSI に何か書いてあると思うのですが、.NET は何

    を見ればいいのでしょうね。

     

    それから、「0.5 を足す」の件ですが、これも外池さんのように詳細に検討して
    いる訳ではないです。私のレスが、言葉足らずかつ断定的過ぎたようですみま

    せん。丸めの誤差を詳細に検討して自分で作るより、ライブラリを利用した方が
    よさそうだという意味です。問題があったら、ライブラリのせいにできるかもしれ
    ませんし。(笑)

    2008年12月11日 12:43