none
127.99999999999999 + 1.0 が 129.0 になります。 RRS feed

  • 質問

  • C++、C#ですが、
    double a = 127.99999999999999;  
    double b = a + 1.0;
    で,  b=129.0 になります。

     そういうものなのですか。対処の方法等りましたら、お願いします。

    ちなみに  double a = 127.9999999999999;  だと
    想定通り                   128.9999999999999   になります。

    2011年10月28日 2:33

回答

  • 外池です。

    「double は全ての数字を表現できる」は、まったくの誤りです。無理数は全然表現できませんし、有理数の中でも表現できるものはごく僅かです。doubleは、精度の観点では近似して、絶対値の大きさの観点では上限の枠をはめて、実数を表現しているだけです。

    お困りの点について、ご説明がほとんど理解できませんでしたので、何ともアドバイスが難しいのですが、

    ただ、ある微小な量(ajust=0.00000001)を決めてやって、この範囲に入れば無理やりゼロに丸めてしまう・・・、という類の「調整」は、私はアリだと思います。

    一方で、描画の方法も、今一度、ドキュメント類を調べられてはいかがでしょう? float(あるいはsingle)型の浮動小数点数で座標や角度を示したり(GDI+)、double型で指定できたり(WPF)して、あとは、OSやグラフィックカードが適当に滑らかに繋いで描画してくれます。「裂け目」が見えたりすることは少ないと思うのですが・・・。


    (ホームページを再開しました)
    • 編集済み 外池 2011年10月28日 8:20
    • 回答としてマーク クサキ 2011年10月28日 10:00
    2011年10月28日 8:14

すべての返信

  • 外池と申します。まず、結論を申し上げると、「そういうもの」ですね。

    「対処の方法」については、クサキさんが、具体的に何に困っておられるのか、どういう理由でこの計算精度が受け入れられないのか、もう少し詳しく説明してもらえると回答がつきやすいかと思います。


    (ホームページを再開しました)
    2011年10月28日 2:48
  • double は全ての数字を表現できると、
    以前何かの本で読んだような気がしますが、私の思い違いなのですね。

    画像を回転させていますが、90度、180度、360度で画像に線状の亀裂が入ります。
    回転後の数学座標の値が本来は0なのですが、計算結果が極めて0に近い値の時になります。

    四点での補間(双線形補間)をしていますが、X方向だけで考えますと、
    X0=127、 X1=128となるところが、X0=127、 X1=129となり良くありません。

    とりあえず、以下のようにしましたら、良くなりましたが、

    double ajust = 0.00000001;
    if ( - ajust < InX && InX < ajust )
     InX = 0;
    if ( - ajust < InY && InY < ajust )
     InY = 0;

    中心座標を少数にしたら、また亀裂が入りました。
    上記も含め、3箇所調整をしましたら、問題はなくなりました。

    最初の段階の1箇所で調整ができたら良いのですが、全体的に気を付けないとだめなのですか。
    また、ajust = 0.00000001 の値をどのくらいにするかもあります。

    皆さんはどんな風にやられているのですか?

     

     

     

     

    2011年10月28日 7:14
  • 外池です。

    「double は全ての数字を表現できる」は、まったくの誤りです。無理数は全然表現できませんし、有理数の中でも表現できるものはごく僅かです。doubleは、精度の観点では近似して、絶対値の大きさの観点では上限の枠をはめて、実数を表現しているだけです。

    お困りの点について、ご説明がほとんど理解できませんでしたので、何ともアドバイスが難しいのですが、

    ただ、ある微小な量(ajust=0.00000001)を決めてやって、この範囲に入れば無理やりゼロに丸めてしまう・・・、という類の「調整」は、私はアリだと思います。

    一方で、描画の方法も、今一度、ドキュメント類を調べられてはいかがでしょう? float(あるいはsingle)型の浮動小数点数で座標や角度を示したり(GDI+)、double型で指定できたり(WPF)して、あとは、OSやグラフィックカードが適当に滑らかに繋いで描画してくれます。「裂け目」が見えたりすることは少ないと思うのですが・・・。


    (ホームページを再開しました)
    • 編集済み 外池 2011年10月28日 8:20
    • 回答としてマーク クサキ 2011年10月28日 10:00
    2011年10月28日 8:14
  • そこまで細かい精度を求めておられるなら double ではなく decimal を使ってみるのも手だと思います。
    ただし精度が高くなる代わりにパフォーマンスは大きく落ちそうですが・・・

    http://code.msdn.microsoft.com/4-decimal-doublefloat-4754c9ae/


    ひらぽん http://d.hatena.ne.jp/hilapon/
    2011年10月28日 8:23
    モデレータ
  • 画像を自分で計算して回転する場合、
    回転先の座標を計算してそこに描画するのではなく、
    描画位置の回転元座標を計算すると良いらしいです。

    decimal は、描画の計算にはとても不向きだと思います。

    2011年10月28日 8:53
  • 必ずしも、表示が目的ではなく計算に使いたいのです。
    また、高速に計算したいので、GDI+などは使いたくありません。
    OSやグラフィックカードが適当に滑らかに繋いでくれるところを自分で作らなければなりません。

    細かい精度を求めているのではなく、ロジックが破綻してしまうからです。

    描画する領域の点に対する、ソースの画像を計算しています。
    回転ですので、sin, cos があり少数になります。
    一般的に、ソースの画像の4つのピクセルの間になります。
    そこで、4点の座標の輝度を距離に反比例するように
    重み付け平均をとっています。
    座標をX0, X1, Y0, Y1とするとX1-X0 と Y1-Y0は1になるとの前提で
    プログラムを作っているのですが、127.99999999999999となった場合に
    X0=127  X1=129になり プログラムが破綻してしまいました。

    前のメールで書いた
    double ajust = 0.00000001;
    if ( - ajust < InX && InX < ajust )
     InX = 0;
    は数学座標の時のチェックですが、
    スクリーン座標に変換した後、0付近と、幅・高さ付近での同様な
    チェックをすることで、一応一箇所でチェックできるようになりました。
    小数から整数に直すと時はチョット気を使わないといけないようです。
    (まだ、100%大丈夫なのか断定できていません)

     

    2011年10月28日 10:00
  • 外池です。

    なんとなく、ですが、ゴールに近づいておられるような感じはするので良いのですが・・・、すいません、最初のご質問からは、最後に書かれたような事情はまったく読み取れませんでしたので、おそらく、まったくトンチンカンな回答をしていたと思います。

    以下、推測ですが、

    1)ご自身で、ビットマップ画像のレンダリングをプログラムされてるんですね?
    2)ビットマップ画像のある隣り合う4つのピクセルに像を結ぶ、基のデータを探し出す計算をされているんですね? (おそらく、ここに回転の演算あり?) 
    3) で、改めて、ピクセルがどんな色(輝度を含む)になるかを計算している。(ここに逆回転の演算あり?)

    んでもって、回転と逆回転の計算結果が、1ピクセル以上差が出てしまって、「裂け目」が出来たと?

    この手の計算、小数から整数に直すときは、メチャクチャ気を使わないとだめですよ。

    いか、ますますもって推測ですが、3)の計算結果は、最初から2)のピクセル位置にはめ込むものとして、決め打ちすればいいんじゃないですか?

     


    (ホームページを再開しました)
    2011年10月28日 11:13
  • 座標をX0, X1, Y0, Y1とするとX1-X0 と Y1-Y0は1になるとの前提で
    プログラムを作っているのですが、127.99999999999999となった場合に
    X0=127 X1=129になり プログラムが破綻してしまいました。

    なぜ、浮動小数点数型で +1 しないといけないのでしょうか?
    元データが整数単位のピクセル座標で扱えるのなら、以下のようなイメージになりませんか?

    double answer = xxxx;// 座標の計算結果
    int x0 = (int)answer;
    int x1 = x0 + 1;
    double x0_ratio = answer - x0; // 割合の計算
    double x1_ratio = x1 - answer;
    double result = data[x0] * x0_ratio + data[x1] * x1_ratio;
    

    なお、double 型の比較は == でやると、今回のケースのように一致しないことがあります。
    期待結果との差分が一定以下であるかどうかで判断しないといけないこともよくあります。
    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2011年10月28日 12:49
    モデレータ
  • 表示は基本的なテーマでなく、テーマはモノクロ画像の数値計算です。
    書き込み方主導でfor loopしてまして、逆回転1回だけです。

    > この手の計算、小数から整数に直すときは、メチャクチャ気を使わないとだめですよ。
    そのようですね。適当にやっていたら、途中から重たくなりますね。

    > なぜ、浮動小数点数型で +1 しないといけないのでしょうか?
    最初、そうしていました。今回のような問題を意識していませんでしたので、
    それで問題意識はありませんでした。
    現在はAzulenさんが言われるように整数の値にして+1しています。

    今回、最初の修正で
    数学座標段階でのチェック、スクリーン座標でのチェック、整数の値にして+1
    の3箇所も修正が必要で大変だなと思っていました。
    現在、スクリーン座標でのチェックと整数の値にして+1の修正で上手く行っています。

    ただ、事前のチェックをしていたら、浮動小数点数型で +1 しても問題ないと思うのですが、
    なぜか、上手くいっていません

    事前のチェックは0付近とかサイズの最大値付近でのチェックだけしか出来ていません。
    中間地点でも同様なことが起こっているのかどうか良く分かりません。
    (中間地点だと、2ピクセル離れたところと平均をとっても
     人間の目には大して問題を感じていないだけかもしれません)
    中間地点でのこの問題の対応はどうするのでしょう。

    > double 型の比較は == でやると、今回のケースのように一致しないことがあります。
    これも肝に銘じてプログラムを組んだ方が良いようですね。

     

    2011年10月29日 2:00
  • こんにちは。

    少し気になったので、質問ですが・・・。

    2 回目の投稿で、

    画像を回転させていますが、90度、180度、360度で画像に線状の亀裂が入ります。 

    とありますが、オイラー角で言うところの360度での、任意の角度への回転をやっておられるのでしょうか?
    それとも、90、180、360度という90度単位での回転のみでしょうか?
    90度単位での回転であれば、sin、cos は必要ないと思うのですが・・・。

    中間地点でのこの問題の対応はどうするのでしょう。

    この点に関してですが、変換後の 1 ピクセルを 0.0 ~ 1.0 ( 実際は 0.999999・・・) の幅と高さを持っている四角形として考えます。
    変換後の 1 ピクセル分の四角形の 4 頂点が、元画像での何処になるかを計算し、元画像のピクセル値に応じた重み演算を行う事になります。

    ググッてみたら、ちょうどいいページがあったので、ご紹介しておきます。

    C言語による画像回転処理について


    • 編集済み ミッヒー 2011年10月29日 5:27 誤記修正
    2011年10月29日 5:22
  • ただ、事前のチェックをしていたら、浮動小数点数型で +1 しても問題ないと思うのですが、
    なぜか、上手くいっていません

    事前にチェックしても、期待する数値ぴったりに補正するのは困難です。
    一度、浮動小数点数の内部表現や演算について調査してください。

    このため、やりたいことにあった形のテクニックが必要になってきます。
    画像の回転であれば、回転後の画像のピクセルを基準にループさせ、元座標を演算で求める形でしょうか。その際、近傍ピクセルからデータをとってきて重み付けして加算することになります。その過程で、前述の私の演算式に似たような処理になります。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2011年10月29日 12:11
    モデレータ
  • もっというとBitmapクラスならImage.RotateFlipメソッドが使えますし…。行列変換とか云々…。
    2011年10月29日 13:21
  • > とありますが、オイラー角で言うところの360度での、任意の角度への回転をやっておられるのでしょうか?
    > それとも、90、180、360度という90度単位での回転のみでしょうか?
    任意の角度の回転です。基本的には上手くいっています。
    ただ、180度などの時に計算結果が0になるべきところが、
    0に近い非常に小さな値になり問題が発生することが、今回のテーマでした。

    > この点に関してですが、変換後の 1 ピクセルを 0.0 ~ 1.0 ( 実際は 0.999999・・・) の
    > 幅と高さを持っている四角形として考えます。
    > 変換後の 1 ピクセル分の四角形の 4 頂点が、元画像での何処になるかを計算し、
    > 元画像のピクセル値に応じた重み演算を行う事になります。
    面白そうな考え方のようです。参考にさせていただきます。
    また、HPのご案内ありがとうございます。

    > このため、やりたいことにあった形のテクニックが必要になってきます。
    > 画像の回転であれば、回転後の画像のピクセルを基準にループさせ、元座標を演算で求める形でしょうか。
    > その際、近傍ピクセルからデータをとってきて重み付けして加算することになります。
    > その過程で、前述の私の演算式に似たような処理になります。
    現在、この形で上手く行っています。

    今回は円でしたが、次にもっと複雑な幾何変換がありまして、小数の扱いを
    適切にする必要があります。どうも、ありがとうございました。

     

    2011年11月1日 1:17