4


3

与えられた横座標xで線上の点の縦座標yを計算しています。 線は、2つの端点座標(x 0、y 0)(x 1、y 1)で定義されます。 終点座標は浮動小数点数であり、GPUで使用するには浮動小数点精度で計算を実行する必要があります。

数学、そしてそれゆえ素朴な実装は些細なことです。

t =(x − x 0)/(x 1 −x 0)とすると、y =(1 − t)* y 0 t * y 1 = y 0 t *(y 1 −y 0)となる。

問題はx1 - x0が小さいときです。 その結果、キャンセルエラーが発生します。 x - x0のいずれかと組み合わせると、除算ではtに大きな誤差が生じると予想されます。

問題は、より良い精度でyを決定するための別の方法があるかどうかということです。

すなわち 最初に(x - x 0)*(y 1 - y 0)を計算し、その後で(x 1 - x 0)で除算する必要がありますか。

差y1 - y0は常に大きくなります。

8 Answer


3


根本的な問題は根本的な問題です。 (x1-x0)が小さいとき、それはx1とx0の仮数に数ビットしかないことを意味します。 そしてさらに拡大すると、x0とx1の間には限られた数のフロートしかありません。 E.g. 仮数の下位4ビットだけが異なる場合、それらの間には最大14個の値があります。

最適なアルゴリズムでは、「+ t +」という用語はこれらの下位ビットを表します。 そして例を続けると、x0とx1が4ビット違うと、tは16個の値しか取れません。 これらの可能な値の計算はかなり堅牢です。 3E0 / 14E0または3E-12 / 14E-12のどちらを計算しても、結果は数学的な3/14の値に近くなります。

式には、0 ⇐ t ⇐ 1なので、y0 ⇐ y ⇐ y1を持つという追加の利点があります。

(私はあなたがフロート表現について十分に知っていると仮定して、そしてそれ故「(x1-x0)は小さい」は本当に「x1とx0自身の値に対して小さい」ことを意味します。 1E-1の差は、x0 = 1E3のときは小さいが、x0 = 1E-6のときは大きい。


2


あなたはQtの "QLine"(私が正しく覚えているなら)ソースを見ているかもしれません。彼らは、 "Graphics Gems"の本の1つから引用した交差判定アルゴリズムを実装しています(参照はコードコメントに含まれている必要があります。2年前にEDonkeyに掲載されました)。計算が与えられたビット幅で行われるときの与えられたスクリーン解像度(私が間違っていなければそれらは固定小数点算術を使う)。


1


もし可能なら、abs(x1-x0)<abs(y1-y0)に応じて、計算に2つのケースを導入できます。 垂直の場合abs(x1-x0)<abs(y1-y0)では、xからのyの代わりにyからxを計算します。

編集します。 他の可能性は、二分検索の変形を用いてビットごとに結果を得ることであろう。 これは遅くなりますが、極端な場合は結果が改善される可能性があります。

// Input is X
x min = min(x 0、x 1)。 x max = max(x 0、x 1); ymin = min(y0、y1); ymax = max(y0、y1)。 for(int i = 0; i <20; i)// 20ビットの結果を得る{xmid =(xmin xmax)* 0.5; ymid =(yminymax)* 0.5。 if(x <xmid){xmax = xmid; ymax = ymid; } //前半その他{xmin = xmid;} ymin = ymid; } // 後半 }
// Output is some value in [ymin,ymax]
Y = ymin。


1


異なる表現の効果を比較するためにベンチマークプログラムを実装しました。

倍精度を使用してyを計算し、次に、式が異なる単精度を使用してyを計算しました。

これがテストされた表現です:

インラインdouble getYDbl(倍精度x、倍精度x0、倍精度y0、倍精度x1、倍精度y1){倍精度定数t =(x  -  x 0)/(x 1  -  x 0); y0 t *(y1  -  y0)を返します。 }

インラインフロートgetYFlt1(フロートx、フロートx0、フロートy0、フロートx1、フロートy1){double const t =(x  -  x 0)/(x 1  -  x 0); y0 t *(y1  -  y0)を返します。 }

インラインフロートgetYFlt2(フロートx、フロートx0、フロートy0、フロートx1、フロートy1){double const t =(x  -  x 0)*(y 1  -  y 0); y0 t /(x1  -  x0)を返します。 }

インラインフロートgetYFlt3(フロートx、フロートx0、フロートy0、フロートx1、フロートy1){double const t =(y1  -  y0)/(x1  -  x0); y0 t *(x  -  x0)を返します。 }

インラインフロートgetYFlt4(フロートx、フロートx0、フロートy0、フロートx1、フロートy1){double const t =(x1  -  x0)/(y1  -  y0); y0(x  -  x0)/ tを返します。 }

倍精度の結果と単精度の結果の差の平均とstdDevを計算しました。

その結果、1000と10Kのランダムな値セットには平均で何もありません。 私はgと同様に最適化の有無にかかわらずiccコンパイラを使いました。

偽の値を除外するためにisnan()関数を使用しなければならなかったことに注意してください。 私はこれらの結果が違いや部門のアンダーフローのせいだと思う。

コンパイラが式を並べ替えるかどうかはわかりません。

とにかく、このテストの結論は、上記の式の並べ替えは計算精度に影響しないということです。 誤差は(平均して)同じままです。


0


あなたのソースデータがすでにフロートであるならば、あなたはすでに根本的な不正確さを持っています。

さらに説明すると、これをグラフィカルに行っているかどうかを想像してください。 あなたは2枚の方眼紙を持ち、2点がマークされています。

ケース1:これらの点は非常に正確であり、非常に鋭い鉛筆で印が付けられています。 それはそれらを結ぶ線を引くのが簡単で、そしてxが与えられたらyを得るのは簡単です(あるいはその逆)。

ケース2:これらのポイントはビンゴのマーカーのように、太ったフェルトペンでマークされています。 明らかにあなたが描く線はそれほど正確ではないでしょう。 あなたはスポットの中心を通り抜けますか? 上端? 下端? 一方の上、もう一方の下? 明らかに多くの異なるオプションがあります。 2つのドットが互いに接近していると、変動はさらに大きくなります。

フロートはそれらが数を表す方法のためにそれら自身に固有のある程度の不正確さを持っています。 世界ではこれを補うことができるアルゴリズムはありません。 不正確なデータを入力、不正確なデータを出力


0


x0とx1の間の距離が小さいかどうか、すなわち fabs(x 1 - x 0)<eps。 その場合、線は座標系のy軸に平行です。 あなたはその行のy値をxに依存して計算することはできません。 あなたは無限に多くのy値を持っているので、この場合を別様に扱う必要があります。


0


次のようなものを計算するのはどうですか。

t = sign * power2(sqrt(abs(x  -  x0))/ sqrt(abs(x1  -  x0)))

この考えは、low(x1-x0)の影響が少ない数学的等価式を使用することです。 (私が書いたものがこの基準に合うかどうかわからない)


0


MSaltersが言ったように、問題は元のデータに既にあります。

内挿/外挿には勾配が必要です。これは、与えられた条件ではすでに精度が低くなっています(原点からかなり離れた非常に短い線分では最悪)。

アルゴリズムの選択はこの正確さの損失を取り戻すことはできません。 私の直感は、エラーが減算ではなく、工夫ではなく導入されているので、異なる評価順序では事態が変わることはないということです。

'' '' '

*考え:*線が生成されるときより正確なデータがあれば、表現を((x0、y0)、(x1、y1))から(x0、y0、angle、length)に変更できます。 あなたは角度や傾斜を保存することができます、傾斜はポールを持っています、しかし角度は三角関数を必要とします…​ 醜い。

もちろん、エンドポイントが頻繁に必要で、追加のデータを保存できないほど多くの行がある場合、これは機能しません。私はわかりません。 しかし、おそらくあなたのニーズに合った別の表現があるでしょう。

ほとんどの場合、doubleは十分な解像度を持ちますが、それでもワーキングセットも2倍になります。