2009-05-22 9 views
3

Je calcule l'ordonnée y d'un point sur une ligne à une abscisse x donnée. La ligne est définie par ses deux coordonnées (x0, y0) (x1, y1). Les coordonnées des points d'extrémité sont des flottants et le calcul doit être effectué avec une précision flottante pour une utilisation dans GPU.Calcul d'ordonnée d'intersection de lignes le plus précis avec des flottants?

Les mathématiques, et donc l'implémentation naïve, sont triviales. Soit t = (x - x0)/(x1 - x0), alors y = (1 - t) * y0 + t * y1 = y0 + t * (y1 - y0).

Le problème est lorsque x1 - x0 est petit. Le résultat introduira une erreur d'annulation. Quand il est combiné avec celui de x - x0, dans la division j'attends une erreur significative dans t.

La question est de savoir s'il existe une autre façon de déterminer y avec une meilleure précision?

est-ce que je devrais calculer (x - x0) * (y1 - y0) en premier et diviser par (x1 - x0) après?

La différence y1 - y0 sera toujours grande.

+0

AFAIK, tous les GPU les plus récents ont ces instructions uniques, donc ça devrait être rapide. –

+3

Si x est compris entre x0 et x1, il n'y a pas de grosse erreur. Lorsque vous calculez t, vous traitez des valeurs du même ordre de grandeur: x-x0, x1-x0. –

Répondre

3

Dans une large mesure, votre problème sous-jacent est fondamental. Quand (x1-x0) est petit, cela signifie qu'il y a seulement quelques bits dans la mantisse de x1 et x0 qui diffèrent. Et par extension, il n'y a qu'un nombre limité de flotteurs entre x0 et x1. Par exemple.si seulement les 4 bits inférieurs de la mantisse diffèrent, il y a au plus 14 valeurs entre eux.

Dans votre meilleur algorithme, le terme t représente ces bits inférieurs. Et pour continuer ou exemple, si x0 et x1 diffèrent de 4 bits, alors t ne peut prendre que 16 valeurs. Le calcul de ces valeurs possibles est assez robuste. Que vous calculiez 3E0/14E0 ou ​​3E-12/14E-12, le résultat sera proche de la valeur mathématique de 3/14.

Votre formule a l'avantage supplémentaire d'avoir y0 < = y < = y1, puisque 0 < = t < = 1

(je suppose que vous en savez assez sur les représentations de flotteur, et par conséquent « (x1 -x0) est petit "signifie vraiment" petit, par rapport aux valeurs de x1 et x0 eux-mêmes "Une différence de 1E-1 est petit quand x0 = 1E3 mais grand si x0 = 1E-6)

+0

Je suis entièrement d'accord avec votre analyse. x0 et x1 ne différeront que par quelques bits moins significatifs et x sera une valeur dans cette petite plage représentable par le flottant. Quelle conclusion pouvons-nous en tirer? Il n'y a aucun moyen d'obtenir une meilleure précision des résultats qu'en utilisant l'implémentation mathématique directe. Cela expliquerait les résultats observés. – chmike

+1

Ouais, c'est ma conclusion. Les valeurs que vous trouvez pour y ne s'amélioreraient pas significativement si vous faisiez les calculs intermédiaires sur les doubles - vous atteindriez les mêmes limites en arrondissant le résultat à float. – MSalters

1

Si vous avez la possibilité de le faire, vous pouvez introduire deux cas dans votre calcul, en fonction de abs (x1-x0) < abs (y1-y0). Dans le cas vertical abs (x1-x0) < abs (y1-y0), calculer x à partir de y au lieu de y à partir de x.

EDIT. Une autre possibilité serait d'obtenir le résultat bit par bit en utilisant une variante de recherche dichotomique. Ce sera plus lent, mais peut améliorer le résultat dans des cas extrêmes.

// Input is X 
xmin = min(x0,x1); 
xmax = max(x0,x1); 
ymin = min(y0,y1); 
ymax = max(y0,y1); 
for (int i=0;i<20;i++) // get 20 bits in result 
{ 
    xmid = (xmin+xmax)*0.5; 
    ymid = (ymin+ymax)*0.5; 
    if (x < xmid) { xmax = xmid; ymax = ymid; } // first half 
    else { xmin = xmid; ymin = ymid; } // second half 
} 
// Output is some value in [ymin,ymax] 
Y = ymin; 
+0

Ce n'est malheureusement pas possible. Je dois trouver y pour une valeur x donnée. – chmike

+0

Et ce n'est même pas possible si la ligne est parallèle à l'axe y. Voir mon message. – ralphtheninja

+0

C'est la méthode décrite dans l'algorithme de découpage de Cohen-Sutherland. C'est intelligent mais comme tu dis pas très efficace. J'ai pensé à le convertir en calcul entier afin que nous sachions combien de bits précis nous avons. – chmike

0

Si vos données sources sont déjà flottantes, vous avez déjà une imprécision fondamentale.

Pour expliquer plus en détail, imaginez si vous le faites graphiquement. Vous avez une feuille 2D de papier millimétré, et 2 points marqués.

Cas 1: Ces points sont très précis, et ont été marqués avec un crayon très pointu. Il est facile de dessiner la ligne qui les relie, et facile d'y obtenir x (ou vice versa). Cas 2: Ces pointes ont été marquées d'un gros feutre, comme un marqueur de bingo. Clairement la ligne que vous dessinez sera moins précise. Passez-vous par le centre des spots? Le bord supérieur? Le bord inférieur? En haut de l'un, en bas de l'autre? Clairement, il y a beaucoup d'options différentes. Si les deux points sont proches l'un de l'autre, la variation sera encore plus grande. Les flotteurs ont un certain niveau d'imprécision inhérent à eux, en raison de la façon dont ils représentent les nombres, dans la mesure où ils correspondent plus au cas 2 qu'au cas 1 (ce qui pourrait être l'équivalent d'un librray de précision arbitraire). Aucun algorithme dans le monde ne peut compenser cela. Données imprécises dans, Données imprécises sur

+1

Vous avez raison sur la précision du flotteur, mais vous avez tort sur votre dernière phrase. Voir http://docs.sun.com/source/806-3568/ncg_goldberg.html. – chmike

+0

Oups. Après avoir relu, vous abordez le problème de la précision de la représentation où j'aborde le problème de précision dans le calcul. Lors du calcul de la moyenne ou de la variance, l'algorithme de Knuth fournit une meilleure précision que l'implémentation naïve, même s'il est mathématiquement correct. – chmike

+0

Il y a des moments où cette inexactitude fondamentale a plus d'effet. par exemple considérer la différence dans le résultat dans 5/(5-x) quand x est 4.002 vs 4.001 vs 4.0, et quand x est 5.002 vs 5.001 vs 5.0. plus proche de 5, l'imprécision a plus d'effet. ici, "Le problème est quand x1 - x0 est petit.". –

0

Vérifiez que la distance entre x0 et x1 est petite, c'est-à-dire que fabs (x1 - x0) < eps. Ensuite, la ligne est parallèle à l'axe y du système de coordonnées, c'est-à-dire que vous ne pouvez pas calculer les valeurs y de cette ligne en fonction de sur x. Vous avez infini de nombreuses valeurs y et vous devez donc traiter ce cas différemment.

+0

Vous pouvez supposer que la condition x0 chmike

+0

Je ne pense pas que vous comprenez ce que je veux dire. "x0 ralphtheninja

+0

Voir la question: dy est toujours grand, donc la ligne ne sera jamais parallèle à l'axe x. En fait, dx <= dy. – chmike

0

quelque chose comme:

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

L'idée est d'utiliser une formule mathématique équivalente dans laquelle low (x1-x0) a moins d'effet. (Je ne sais pas si celui que j'ai écrit correspond à ce critère)

+0

J'ai ajouté cette expression au code de test et elle a donné le même résultat que les autres méthodes d'erreur moyenne et stdDev. Donc, aucun avantage. A côté de cela ne semble pas très efficace. À moins que les compilateurs soient assez intelligents pour supprimer le sqrt. – chmike

+0

Merci, En ce qui concerne l'erreur moyenne, avez-vous vérifié l'erreur divisée par la valeur réelle? Il y a une différence entre 5.1 5.0 et 0.0 0.1, le plus tard est une erreur plus grande proportionnellement à la valeur. –

+0

Comment cela améliore-t-il les choses? – peterchen

1

J'ai mis en œuvre un programme de référence pour comparer l'effet de l'expression différente.

J'ai calculé y en utilisant une double précision, puis j'ai calculé en utilisant une seule précision avec des expressions différentes.

Voici l'expression testée:

inline double getYDbl(double x, double x0, double y0, double x1, double y1) 
{ 
    double const t = (x - x0)/(x1 - x0); 
    return y0 + t*(y1 - y0); 
} 

inline float getYFlt1(float x, float x0, float y0, float x1, float y1) 
{ 
    double const t = (x - x0)/(x1 - x0); 
    return y0 + t*(y1 - y0); 
} 

inline float getYFlt2(float x, float x0, float y0, float x1, float y1) 
{ 
    double const t = (x - x0)*(y1 - y0); 
    return y0 + t/(x1 - x0); 
} 

inline float getYFlt3(float x, float x0, float y0, float x1, float y1) 
{ 
    double const t = (y1 - y0)/(x1 - x0); 
    return y0 + t*(x - x0); 
} 

inline float getYFlt4(float x, float x0, float y0, float x1, float y1) 
{ 
    double const t = (x1 - x0)/(y1 - y0); 
    return y0 + (x - x0)/t; 
} 

I calculé la moyenne et stdDev de la différence entre le résultat double précision et le résultat de simple précision.

Le résultat est qu'il n'y en a aucun en moyenne plus de 1000 et 10K ensembles de valeurs aléatoires. J'ai utilisé le compilateur icc avec et sans optimisation ainsi que g ++.

Notez que j'ai dû utiliser la fonction isnan() pour filtrer les valeurs fausses. Je soupçonne que ceux-ci résultent d'un débordement dans la différence ou la division.

Je ne sais pas si les compilateurs réorganisent l'expression. Quoi qu'il en soit, la conclusion de ce test est que les réarrangements de l'expression ci-dessus n'ont aucun effet sur la précision de calcul. L'erreur reste la même (en moyenne).

+0

Pour une meilleure comparaison, prenez toutes les entrées et renvoyez les valeurs comme des flottants; haut/bas pour doubler comme approprié. Votre question est de savoir si la précision limitée est due au calcul avec des flotteurs. Moi et d'autres soutiennent que c'est la précision limitée de l'entrée elle-même, pas du calcul, que vous voyez. – MSalters

+0

Avez-vous essayé d'utiliser des valeurs dans lesquelles (x1 - x0) est petit? –

+0

De plus, quels paramètres d'optimisation avez-vous utilisés? L'optimiseur peut réorganiser l'ordre d'évaluation des expressions. – peterchen

2

Vous pouvez jeter un coup d'œil aux sources "QLine" de Qt (si je me souviens bien); ils ont implémenté un algorithme de détermination des intersections tiré d'un des livres "Graphics Gems" (la référence doit être dans les commentaires du code, le livre était sur EDonkey il y a quelques années), qui, à son tour, a des garanties sur l'applicabilité résolution d'écran donnée lorsque les calculs sont effectués avec une largeur de bit donnée (ils utilisent l'arithmétique à virgule fixe si je ne me trompe pas).

0

Comme indiqué MSalters, le problème est déjà dans les données d'origine.

L'interpolation/extrapolation nécessite la pente, qui a déjà une faible précision dans les conditions données (pire pour les segments de ligne très courts éloignés de l'origine).

Choix de l'algorithme canot retrouver cette perte de précision.Mon intuition est que l'ordre d'évaluation différent ne changera pas les choses, car l'erreur est introduite par les soustractions, pas la devision.


Idée:
Si vous avez des données plus précises lorsque les lignes sont générées, vous pouvez changer la représentation de ((x0, y0), (x1, y1)) à (x0, y0, angle, longueur). Vous pouvez stocker l'angle ou la pente, la pente a un poteau, mais l'angle nécessite des fonctions trigonométriques ... moche.

Bien sûr, cela ne fonctionnera pas si vous avez besoin du point de terminaison fréquemment, et vous avez tellement de lignes que vous ne pouvez pas stocker de données supplémentaires, je n'en ai aucune idée. Mais peut-être existe-t-il une autre représentation qui fonctionne bien pour vos besoins. Les doubles ont suffisamment de résolution dans la plupart des situations, mais cela doublerait également le jeu de travail. Pouvez-vous utiliser des fonctions trigonométriques comme sin ou cos?

+0

Je suis d'accord avec vous sur la précision des données d'entrée. Mais la question portait sur le processus de calcul. La méthode de calcul décrite pourrait-elle gâcher la précision réduite que nous avons? – chmike