"kanze" writes:
Quant a l'histoire, si j'ai bien compris, a cette epoque les
machines etaient decimales ou binaires; celles destinees aux
charges "commerciales" -- remplacement des tabulatrices --
decimales, celles destinees aux charges "scientifiques" --
calculs -- binaires. Donc j'ai l'impression que les flottants
dans leur domaine d'utilisation naturel sont classiquement
binaires.
Plus ou moins. IBM, au moins, a toujours été une exception, et
l'architecture 360 offrait à la fois l'arithmetique décimal (BCD)
pour les applications commerciales, et des virgules flottants
hexadécimaux pour les calculs numériques (et se vendaient bien dans
les deux domaines).
IBM 360, c'est pour moi le moment ou la separation entre machine
commerciale et machine scientifique commence a s'estomper d'un point
de vue architecturale.
Apres, il n'y a quasiment plus de machines decimales. Les
machines adressables par mots -- une caracteristique
essentielle de l'aspect scientifique -- ont survecu plus
longtemps; peut-etre parce qu'IBM etait moins dominant sur ce
marche.
(Question implementation, d'apres ce que j'ai lu, il y avait
souvent une base binaire soujacente au comportement
architectural decimal).
Au plus bas niveau... J'ai entendu parlé une fois d'une machine
expérimentale qui utilisait le base 3,
Russe, un prototype construit d'apres ce que j'ai compris. Elle etait
binaire mais les bits etaient groupes par deux et le quatrieme etat
servait a faire du controle d'erreur.
et d'après ce que j'ai entendu dire, une partie au moins des tout
premiers 8087 était implémenté en base 4 (mais ça ne paraissait pas
à l'exterieur -- c'était pour reduire la place nécessaire pour le
microcode, je crois).
S'il y a du base 4, je parie que c'est a nouveau en manipulant des
groupes de 2 bits.
"kanze" <kanze@gabi-soft.fr> writes:
Quant a l'histoire, si j'ai bien compris, a cette epoque les
machines etaient decimales ou binaires; celles destinees aux
charges "commerciales" -- remplacement des tabulatrices --
decimales, celles destinees aux charges "scientifiques" --
calculs -- binaires. Donc j'ai l'impression que les flottants
dans leur domaine d'utilisation naturel sont classiquement
binaires.
Plus ou moins. IBM, au moins, a toujours été une exception, et
l'architecture 360 offrait à la fois l'arithmetique décimal (BCD)
pour les applications commerciales, et des virgules flottants
hexadécimaux pour les calculs numériques (et se vendaient bien dans
les deux domaines).
IBM 360, c'est pour moi le moment ou la separation entre machine
commerciale et machine scientifique commence a s'estomper d'un point
de vue architecturale.
Apres, il n'y a quasiment plus de machines decimales. Les
machines adressables par mots -- une caracteristique
essentielle de l'aspect scientifique -- ont survecu plus
longtemps; peut-etre parce qu'IBM etait moins dominant sur ce
marche.
(Question implementation, d'apres ce que j'ai lu, il y avait
souvent une base binaire soujacente au comportement
architectural decimal).
Au plus bas niveau... J'ai entendu parlé une fois d'une machine
expérimentale qui utilisait le base 3,
Russe, un prototype construit d'apres ce que j'ai compris. Elle etait
binaire mais les bits etaient groupes par deux et le quatrieme etat
servait a faire du controle d'erreur.
et d'après ce que j'ai entendu dire, une partie au moins des tout
premiers 8087 était implémenté en base 4 (mais ça ne paraissait pas
à l'exterieur -- c'était pour reduire la place nécessaire pour le
microcode, je crois).
S'il y a du base 4, je parie que c'est a nouveau en manipulant des
groupes de 2 bits.
"kanze" writes:
Quant a l'histoire, si j'ai bien compris, a cette epoque les
machines etaient decimales ou binaires; celles destinees aux
charges "commerciales" -- remplacement des tabulatrices --
decimales, celles destinees aux charges "scientifiques" --
calculs -- binaires. Donc j'ai l'impression que les flottants
dans leur domaine d'utilisation naturel sont classiquement
binaires.
Plus ou moins. IBM, au moins, a toujours été une exception, et
l'architecture 360 offrait à la fois l'arithmetique décimal (BCD)
pour les applications commerciales, et des virgules flottants
hexadécimaux pour les calculs numériques (et se vendaient bien dans
les deux domaines).
IBM 360, c'est pour moi le moment ou la separation entre machine
commerciale et machine scientifique commence a s'estomper d'un point
de vue architecturale.
Apres, il n'y a quasiment plus de machines decimales. Les
machines adressables par mots -- une caracteristique
essentielle de l'aspect scientifique -- ont survecu plus
longtemps; peut-etre parce qu'IBM etait moins dominant sur ce
marche.
(Question implementation, d'apres ce que j'ai lu, il y avait
souvent une base binaire soujacente au comportement
architectural decimal).
Au plus bas niveau... J'ai entendu parlé une fois d'une machine
expérimentale qui utilisait le base 3,
Russe, un prototype construit d'apres ce que j'ai compris. Elle etait
binaire mais les bits etaient groupes par deux et le quatrieme etat
servait a faire du controle d'erreur.
et d'après ce que j'ai entendu dire, une partie au moins des tout
premiers 8087 était implémenté en base 4 (mais ça ne paraissait pas
à l'exterieur -- c'était pour reduire la place nécessaire pour le
microcode, je crois).
S'il y a du base 4, je parie que c'est a nouveau en manipulant des
groupes de 2 bits.
Sylvain writes:dans tes benchs tu accumules 1.000.000 de valeurs flottantes
de type float, or un float à une précision de 10^-6, en
sommer 10^6 va obligeatoirement faire perdre toute précision
de l'élément ajouté par rapport à l'accu courant - ie va
générer un débordement de précision et revient à faire un
inf + epsilon en espérant trouver l'epsilon dans le nouveau
résultat.
Je ne comprends pas.
Dans le cadre donne ci-dessus, on peut prouver des bornes
superieures pour l'erreur introduite pour deux des algorithmes
(sumPriority, sumCompensated) qui sont de l'ordre de quelques
ULP (si j'ai bonne memoire, moins d'un ULP pour
sumCompensated, autrement dit, une des valeurs qui encadrent
le resultat exact).
template<typename T> T sumPart(float const* array, size_t size)
{
size = (size_t) sqrt(size);
T result = 0.0;
float const* t = array;
for (size_t k = 0; k < size; ++k){
T inter = 0.0;
for (size_t i = 0; i < size; ++i, ++t)
inter += *t;
result += inter;
}
return result;
}
donne également le bon résultat pour un type T float.
Meme principe que sumBinary, mais deux niveaux seulement. Je suis
certain qu'il y a moyen de contruire des jeux de donnees
(vraissemblablement meme avec toutes les valeurs comprises entre 0.5
et 1) pour lequel on a un resultat moins precis que
sumCompensated<float>.
Sylvain <noSpam@mail.net> writes:
dans tes benchs tu accumules 1.000.000 de valeurs flottantes
de type float, or un float à une précision de 10^-6, en
sommer 10^6 va obligeatoirement faire perdre toute précision
de l'élément ajouté par rapport à l'accu courant - ie va
générer un débordement de précision et revient à faire un
inf + epsilon en espérant trouver l'epsilon dans le nouveau
résultat.
Je ne comprends pas.
Dans le cadre donne ci-dessus, on peut prouver des bornes
superieures pour l'erreur introduite pour deux des algorithmes
(sumPriority, sumCompensated) qui sont de l'ordre de quelques
ULP (si j'ai bonne memoire, moins d'un ULP pour
sumCompensated, autrement dit, une des valeurs qui encadrent
le resultat exact).
template<typename T> T sumPart(float const* array, size_t size)
{
size = (size_t) sqrt(size);
T result = 0.0;
float const* t = array;
for (size_t k = 0; k < size; ++k){
T inter = 0.0;
for (size_t i = 0; i < size; ++i, ++t)
inter += *t;
result += inter;
}
return result;
}
donne également le bon résultat pour un type T float.
Meme principe que sumBinary, mais deux niveaux seulement. Je suis
certain qu'il y a moyen de contruire des jeux de donnees
(vraissemblablement meme avec toutes les valeurs comprises entre 0.5
et 1) pour lequel on a un resultat moins precis que
sumCompensated<float>.
Sylvain writes:dans tes benchs tu accumules 1.000.000 de valeurs flottantes
de type float, or un float à une précision de 10^-6, en
sommer 10^6 va obligeatoirement faire perdre toute précision
de l'élément ajouté par rapport à l'accu courant - ie va
générer un débordement de précision et revient à faire un
inf + epsilon en espérant trouver l'epsilon dans le nouveau
résultat.
Je ne comprends pas.
Dans le cadre donne ci-dessus, on peut prouver des bornes
superieures pour l'erreur introduite pour deux des algorithmes
(sumPriority, sumCompensated) qui sont de l'ordre de quelques
ULP (si j'ai bonne memoire, moins d'un ULP pour
sumCompensated, autrement dit, une des valeurs qui encadrent
le resultat exact).
template<typename T> T sumPart(float const* array, size_t size)
{
size = (size_t) sqrt(size);
T result = 0.0;
float const* t = array;
for (size_t k = 0; k < size; ++k){
T inter = 0.0;
for (size_t i = 0; i < size; ++i, ++t)
inter += *t;
result += inter;
}
return result;
}
donne également le bon résultat pour un type T float.
Meme principe que sumBinary, mais deux niveaux seulement. Je suis
certain qu'il y a moyen de contruire des jeux de donnees
(vraissemblablement meme avec toutes les valeurs comprises entre 0.5
et 1) pour lequel on a un resultat moins precis que
sumCompensated<float>.
C'en est une autre, alors. Celle dont j'ai entendu parler,
c'était en France. Et c'était réelement du base trois. Mais je
ne sais pas si elle a jamais été réalisée -- je crois que
c'était une conception théorique, sur papier.
et d'après ce que j'ai entendu dire, une partie au moins des
tout premiers 8087 était implémenté en base 4 (mais ça ne
paraissait pas à l'exterieur -- c'était pour reduire la place
nécessaire pour le microcode, je crois).
S'il y a du base 4, je parie que c'est a nouveau en manipulant des
groupes de 2 bits.
Au contraire. Le problème, c'était de gagner de la place dans le ROM
du microcode. La solution, c'était bien un discriminateur à quatre
niveaux de tension, avec des ponts plus ou moins ajuster pour donner
le bon niveau selon les valeurs binaires voulues.
C'en est une autre, alors. Celle dont j'ai entendu parler,
c'était en France. Et c'était réelement du base trois. Mais je
ne sais pas si elle a jamais été réalisée -- je crois que
c'était une conception théorique, sur papier.
et d'après ce que j'ai entendu dire, une partie au moins des
tout premiers 8087 était implémenté en base 4 (mais ça ne
paraissait pas à l'exterieur -- c'était pour reduire la place
nécessaire pour le microcode, je crois).
S'il y a du base 4, je parie que c'est a nouveau en manipulant des
groupes de 2 bits.
Au contraire. Le problème, c'était de gagner de la place dans le ROM
du microcode. La solution, c'était bien un discriminateur à quatre
niveaux de tension, avec des ponts plus ou moins ajuster pour donner
le bon niveau selon les valeurs binaires voulues.
C'en est une autre, alors. Celle dont j'ai entendu parler,
c'était en France. Et c'était réelement du base trois. Mais je
ne sais pas si elle a jamais été réalisée -- je crois que
c'était une conception théorique, sur papier.
et d'après ce que j'ai entendu dire, une partie au moins des
tout premiers 8087 était implémenté en base 4 (mais ça ne
paraissait pas à l'exterieur -- c'était pour reduire la place
nécessaire pour le microcode, je crois).
S'il y a du base 4, je parie que c'est a nouveau en manipulant des
groupes de 2 bits.
Au contraire. Le problème, c'était de gagner de la place dans le ROM
du microcode. La solution, c'était bien un discriminateur à quatre
niveaux de tension, avec des ponts plus ou moins ajuster pour donner
le bon niveau selon les valeurs binaires voulues.
Ce qui est intéressant, c'est que c'est moins précis que sumUp
sur PC. La fonction sumUp est assez simple pour que g++ réussi à
faire tout le calcul dans des régistres à précision étendu --
Ce qui est intéressant, c'est que c'est moins précis que sumUp
sur PC. La fonction sumUp est assez simple pour que g++ réussi à
faire tout le calcul dans des régistres à précision étendu --
Ce qui est intéressant, c'est que c'est moins précis que sumUp
sur PC. La fonction sumUp est assez simple pour que g++ réussi à
faire tout le calcul dans des régistres à précision étendu --
Sylvain writes:tes routines sont intéressantes ! de mon point de vue, elles sont toutes
inadaptées et maladroites (ce n'est pas une provocation!).
En quoi sont-elles inadaptees a l'objectif poursuivi: analyser
l'erreur introduite par differentes manieres de faire la somme
(definie comme etant la difference entre le resultat exact arrondi a
la precision d'un float et le resultat calculer)?
dans tes benchs tu accumules 1.000.000 de valeurs flottantes de type float,
or un float à une précision de 10^-6, en sommer 10^6 va obligeatoirement
faire perdre toute précision de l'élément ajouté par rapport à l'accu
courant - ie va générer un débordement de précision et revient à faire un
inf + epsilon en espérant trouver l'epsilon dans le nouveau résultat.
Je ne comprends pas.
Dans le cadre donne ci-dessus, on peut prouver des bornes superieures
pour l'erreur introduite pour deux des algorithmes (sumPriority,
sumCompensated) qui sont de l'ordre de quelques ULP (si j'ai bonne
memoire, moins d'un ULP pour sumCompensated, autrement dit, une des
valeurs qui encadrent le resultat exact).
template<typename T> T sumPart(float const* array, size_t size)
[...]
Meme principe que sumBinary, mais deux niveaux seulement.
Je suis certain qu'il y a moyen de contruire des jeux de donnees
(vraissemblablement meme avec toutes les valeurs comprises entre 0.5
et 1) pour lequel on a un resultat moins precis que
sumCompensated<float>.
Sylvain <noSpam@mail.net> writes:
tes routines sont intéressantes ! de mon point de vue, elles sont toutes
inadaptées et maladroites (ce n'est pas une provocation!).
En quoi sont-elles inadaptees a l'objectif poursuivi: analyser
l'erreur introduite par differentes manieres de faire la somme
(definie comme etant la difference entre le resultat exact arrondi a
la precision d'un float et le resultat calculer)?
dans tes benchs tu accumules 1.000.000 de valeurs flottantes de type float,
or un float à une précision de 10^-6, en sommer 10^6 va obligeatoirement
faire perdre toute précision de l'élément ajouté par rapport à l'accu
courant - ie va générer un débordement de précision et revient à faire un
inf + epsilon en espérant trouver l'epsilon dans le nouveau résultat.
Je ne comprends pas.
Dans le cadre donne ci-dessus, on peut prouver des bornes superieures
pour l'erreur introduite pour deux des algorithmes (sumPriority,
sumCompensated) qui sont de l'ordre de quelques ULP (si j'ai bonne
memoire, moins d'un ULP pour sumCompensated, autrement dit, une des
valeurs qui encadrent le resultat exact).
template<typename T> T sumPart(float const* array, size_t size)
[...]
Meme principe que sumBinary, mais deux niveaux seulement.
Je suis certain qu'il y a moyen de contruire des jeux de donnees
(vraissemblablement meme avec toutes les valeurs comprises entre 0.5
et 1) pour lequel on a un resultat moins precis que
sumCompensated<float>.
Sylvain writes:tes routines sont intéressantes ! de mon point de vue, elles sont toutes
inadaptées et maladroites (ce n'est pas une provocation!).
En quoi sont-elles inadaptees a l'objectif poursuivi: analyser
l'erreur introduite par differentes manieres de faire la somme
(definie comme etant la difference entre le resultat exact arrondi a
la precision d'un float et le resultat calculer)?
dans tes benchs tu accumules 1.000.000 de valeurs flottantes de type float,
or un float à une précision de 10^-6, en sommer 10^6 va obligeatoirement
faire perdre toute précision de l'élément ajouté par rapport à l'accu
courant - ie va générer un débordement de précision et revient à faire un
inf + epsilon en espérant trouver l'epsilon dans le nouveau résultat.
Je ne comprends pas.
Dans le cadre donne ci-dessus, on peut prouver des bornes superieures
pour l'erreur introduite pour deux des algorithmes (sumPriority,
sumCompensated) qui sont de l'ordre de quelques ULP (si j'ai bonne
memoire, moins d'un ULP pour sumCompensated, autrement dit, une des
valeurs qui encadrent le resultat exact).
template<typename T> T sumPart(float const* array, size_t size)
[...]
Meme principe que sumBinary, mais deux niveaux seulement.
Je suis certain qu'il y a moyen de contruire des jeux de donnees
(vraissemblablement meme avec toutes les valeurs comprises entre 0.5
et 1) pour lequel on a un resultat moins precis que
sumCompensated<float>.
On peut le dire, vue qu'il fait que j'ai des résultats
différents selon le niveau d'optimisation, sans changer ni le
compilateur ni le CPU. (Et sans avoir un comportement indéfini
ni non-spécifié.)
Tu trouves que ce n'est pas étonnant que je n'ai pas obtenu le
même résultat ? J'avais l'impression que tu disais que les
résultats étaient indentiques sur un même processeur. C'est bien
un contre-exemple. (Mais peut-être j'ai mal compris ce que tu
voulais dire.)
Alors, de quoi sers-tu pour comparer, si tu n'utilises ni les
tests d'équalité, ni les tests d'ordonnencement ?
Bien, je supposais que tu disais quelque chose d'utile. Qu'un
résultat soit connu à une précision donnée n'a d'utilité que si
on connaît cette précision. Et comme j'ai montré, cette
précision varie selon le contexte.
Pas de problème -- elle ne sont pas de moi. J'étais simplement
curieux aux différences réeles de précision et de vitesse. (De
prèsque j'ai compris d'après les discussions, le sumCompensated
est plus ou moins la solution standard parmi les spécialistes
dans la numérique.
Je pourrais ajouter qu'ici on a un problème où la précision
supplémentaire des valeurs intermédiaire sur l'Intel a un
avantage. Sur le PC Linux, j'ai exactement les mêmes résultats
dans les tests de précision, quelque soit l'algorithme utilisé.
Pas vraiement. D'abord, les valeurs flottantes sont, par défaut,
toutes dans l'intervalle 0-1,0. Alors, je sais que je n'aurais
pas de débordement de valeur.
Deuxièmement, vouloir savoir le moyen d'un grand
nombre valeurs me semble un problème raisonable
-- une réponse du genre on ne sait pas le faire
avec un ordinateur ne me semble pas acceptable.
[...] (Autant que je me rappelle, Jean-Marc a
posté l'algorithme sumPriority comme un exemple d'un algorithme
« naïf » dans l'autre sens -- c'est la solution trivialement
évident pour réduire l'erreur au maximum, mais à quel coût !)
(Évidemment, dans n'importe quelle utilisation réele,
j'utiliserais double pour l'accumulateur de la somme, et non
float. Ici, le but était en partie de voir le comportement des
algorithmes par rapport aux pertes de précision -- et on voit
plus clairement quand les pertes de précision sont plus
grandes.)
l'autre point corrollaire pertinent est la précision
intermédiaire que tu évoquais; puisque les calculs sont en
boucle le résultat intermédiaire (qui contient qlq chose comme
500000.512345 en fin de boucle) est tronqué à un float (6
chiffres significatifs, donc 500000.) et donc fausse
complètement le résultat.
Il doit l'être, selon la norme C++. D'après les résultats, j'ai
l'impression qu'il ne l'est pas avec le compilateur g++ sur
PC/Linux.
Coût et danger rélatifs. sumBinary est nettement plus rapide que
sumPriority (un ordre de magnitude, quand même), et
Je sais. J'ai utilisé float exprès, vue que le but était de
comparer le comportement des *algorithmes*, par rapport aux
arondi. J'ai fait en sort de maximiser ce que je voulais
mesurer.
comme tu disais,il est important de savoir ce que l'on fait --
et ici il faut comprendre que garder 6 chiffres significatifs
entre 2 opérandes ayant 6 magnitudes d'écart impose de traiter
ces opérations avec 12 chiffres significatifs.
(7 chiffres significatifs, en fait).
Sauf sumUp et sumDown, je crois que je n'ai jamais de cas où il
y a six magnitudes d'écart.
L'idée derrière sumPriority et sumBinary, en fait, c'est
que les magnitudes soient toujours à peu près identiques
sumPriority le garantit (à un prix exhorbitant),
et sumBinary joue sur la statistique -- c'est
d'ailleurs pourquoi dans mes tests de précision j'ai fait des
tests avec le tableau trié ; c'est le cas le plus défavorable
pour sumBinary (en ce qui concerne la précision).
il vient que (array étant un float[]):
double result = 0.0;
for (size_t i = 0; i < size; ++i)
result += array[i];
est bien l'écriture la plus compacte, simple et intuitive qui
donne le bon résultat.
Il s'avère qu'on ne peut pas garantir non plus qu'elle donne le
bon résultat. Il y a toujours des erreurs d'arrondi. Moins
qu'avec float, mais il y en a quand même.
Dans mes tests, j'utilisais les tableaux avec une taille d'une
million d'éléments. Des tableaux assez petits, en somme, mais
j'ai une vieille machine, pas très rapide, qui sert aussi à
d'autres choses, et je n'ai pas voulu la surcharger trop pendant
trop longtemps. J'ai donc modifié mes tests initiaux pour que
les effets de l'arrondi se fassent sentir sur des tableaux plus
petits. (Il faut dire surtout que j'ai assez peu de mémoire. Et
quand le test se met à pager, ça ralentit tout, beaucoup.)
Et qu'est-ce que tu penses que sumBinary fait ?
PC, Linux, g++ :
Floating point tests:
On peut le dire, vue qu'il fait que j'ai des résultats
différents selon le niveau d'optimisation, sans changer ni le
compilateur ni le CPU. (Et sans avoir un comportement indéfini
ni non-spécifié.)
Tu trouves que ce n'est pas étonnant que je n'ai pas obtenu le
même résultat ? J'avais l'impression que tu disais que les
résultats étaient indentiques sur un même processeur. C'est bien
un contre-exemple. (Mais peut-être j'ai mal compris ce que tu
voulais dire.)
Alors, de quoi sers-tu pour comparer, si tu n'utilises ni les
tests d'équalité, ni les tests d'ordonnencement ?
Bien, je supposais que tu disais quelque chose d'utile. Qu'un
résultat soit connu à une précision donnée n'a d'utilité que si
on connaît cette précision. Et comme j'ai montré, cette
précision varie selon le contexte.
Pas de problème -- elle ne sont pas de moi. J'étais simplement
curieux aux différences réeles de précision et de vitesse. (De
prèsque j'ai compris d'après les discussions, le sumCompensated
est plus ou moins la solution standard parmi les spécialistes
dans la numérique.
Je pourrais ajouter qu'ici on a un problème où la précision
supplémentaire des valeurs intermédiaire sur l'Intel a un
avantage. Sur le PC Linux, j'ai exactement les mêmes résultats
dans les tests de précision, quelque soit l'algorithme utilisé.
Pas vraiement. D'abord, les valeurs flottantes sont, par défaut,
toutes dans l'intervalle 0-1,0. Alors, je sais que je n'aurais
pas de débordement de valeur.
Deuxièmement, vouloir savoir le moyen d'un grand
nombre valeurs me semble un problème raisonable
-- une réponse du genre on ne sait pas le faire
avec un ordinateur ne me semble pas acceptable.
[...] (Autant que je me rappelle, Jean-Marc a
posté l'algorithme sumPriority comme un exemple d'un algorithme
« naïf » dans l'autre sens -- c'est la solution trivialement
évident pour réduire l'erreur au maximum, mais à quel coût !)
(Évidemment, dans n'importe quelle utilisation réele,
j'utiliserais double pour l'accumulateur de la somme, et non
float. Ici, le but était en partie de voir le comportement des
algorithmes par rapport aux pertes de précision -- et on voit
plus clairement quand les pertes de précision sont plus
grandes.)
l'autre point corrollaire pertinent est la précision
intermédiaire que tu évoquais; puisque les calculs sont en
boucle le résultat intermédiaire (qui contient qlq chose comme
500000.512345 en fin de boucle) est tronqué à un float (6
chiffres significatifs, donc 500000.) et donc fausse
complètement le résultat.
Il doit l'être, selon la norme C++. D'après les résultats, j'ai
l'impression qu'il ne l'est pas avec le compilateur g++ sur
PC/Linux.
Coût et danger rélatifs. sumBinary est nettement plus rapide que
sumPriority (un ordre de magnitude, quand même), et
Je sais. J'ai utilisé float exprès, vue que le but était de
comparer le comportement des *algorithmes*, par rapport aux
arondi. J'ai fait en sort de maximiser ce que je voulais
mesurer.
comme tu disais,il est important de savoir ce que l'on fait --
et ici il faut comprendre que garder 6 chiffres significatifs
entre 2 opérandes ayant 6 magnitudes d'écart impose de traiter
ces opérations avec 12 chiffres significatifs.
(7 chiffres significatifs, en fait).
Sauf sumUp et sumDown, je crois que je n'ai jamais de cas où il
y a six magnitudes d'écart.
L'idée derrière sumPriority et sumBinary, en fait, c'est
que les magnitudes soient toujours à peu près identiques
sumPriority le garantit (à un prix exhorbitant),
et sumBinary joue sur la statistique -- c'est
d'ailleurs pourquoi dans mes tests de précision j'ai fait des
tests avec le tableau trié ; c'est le cas le plus défavorable
pour sumBinary (en ce qui concerne la précision).
il vient que (array étant un float[]):
double result = 0.0;
for (size_t i = 0; i < size; ++i)
result += array[i];
est bien l'écriture la plus compacte, simple et intuitive qui
donne le bon résultat.
Il s'avère qu'on ne peut pas garantir non plus qu'elle donne le
bon résultat. Il y a toujours des erreurs d'arrondi. Moins
qu'avec float, mais il y en a quand même.
Dans mes tests, j'utilisais les tableaux avec une taille d'une
million d'éléments. Des tableaux assez petits, en somme, mais
j'ai une vieille machine, pas très rapide, qui sert aussi à
d'autres choses, et je n'ai pas voulu la surcharger trop pendant
trop longtemps. J'ai donc modifié mes tests initiaux pour que
les effets de l'arrondi se fassent sentir sur des tableaux plus
petits. (Il faut dire surtout que j'ai assez peu de mémoire. Et
quand le test se met à pager, ça ralentit tout, beaucoup.)
Et qu'est-ce que tu penses que sumBinary fait ?
PC, Linux, g++ :
Floating point tests:
On peut le dire, vue qu'il fait que j'ai des résultats
différents selon le niveau d'optimisation, sans changer ni le
compilateur ni le CPU. (Et sans avoir un comportement indéfini
ni non-spécifié.)
Tu trouves que ce n'est pas étonnant que je n'ai pas obtenu le
même résultat ? J'avais l'impression que tu disais que les
résultats étaient indentiques sur un même processeur. C'est bien
un contre-exemple. (Mais peut-être j'ai mal compris ce que tu
voulais dire.)
Alors, de quoi sers-tu pour comparer, si tu n'utilises ni les
tests d'équalité, ni les tests d'ordonnencement ?
Bien, je supposais que tu disais quelque chose d'utile. Qu'un
résultat soit connu à une précision donnée n'a d'utilité que si
on connaît cette précision. Et comme j'ai montré, cette
précision varie selon le contexte.
Pas de problème -- elle ne sont pas de moi. J'étais simplement
curieux aux différences réeles de précision et de vitesse. (De
prèsque j'ai compris d'après les discussions, le sumCompensated
est plus ou moins la solution standard parmi les spécialistes
dans la numérique.
Je pourrais ajouter qu'ici on a un problème où la précision
supplémentaire des valeurs intermédiaire sur l'Intel a un
avantage. Sur le PC Linux, j'ai exactement les mêmes résultats
dans les tests de précision, quelque soit l'algorithme utilisé.
Pas vraiement. D'abord, les valeurs flottantes sont, par défaut,
toutes dans l'intervalle 0-1,0. Alors, je sais que je n'aurais
pas de débordement de valeur.
Deuxièmement, vouloir savoir le moyen d'un grand
nombre valeurs me semble un problème raisonable
-- une réponse du genre on ne sait pas le faire
avec un ordinateur ne me semble pas acceptable.
[...] (Autant que je me rappelle, Jean-Marc a
posté l'algorithme sumPriority comme un exemple d'un algorithme
« naïf » dans l'autre sens -- c'est la solution trivialement
évident pour réduire l'erreur au maximum, mais à quel coût !)
(Évidemment, dans n'importe quelle utilisation réele,
j'utiliserais double pour l'accumulateur de la somme, et non
float. Ici, le but était en partie de voir le comportement des
algorithmes par rapport aux pertes de précision -- et on voit
plus clairement quand les pertes de précision sont plus
grandes.)
l'autre point corrollaire pertinent est la précision
intermédiaire que tu évoquais; puisque les calculs sont en
boucle le résultat intermédiaire (qui contient qlq chose comme
500000.512345 en fin de boucle) est tronqué à un float (6
chiffres significatifs, donc 500000.) et donc fausse
complètement le résultat.
Il doit l'être, selon la norme C++. D'après les résultats, j'ai
l'impression qu'il ne l'est pas avec le compilateur g++ sur
PC/Linux.
Coût et danger rélatifs. sumBinary est nettement plus rapide que
sumPriority (un ordre de magnitude, quand même), et
Je sais. J'ai utilisé float exprès, vue que le but était de
comparer le comportement des *algorithmes*, par rapport aux
arondi. J'ai fait en sort de maximiser ce que je voulais
mesurer.
comme tu disais,il est important de savoir ce que l'on fait --
et ici il faut comprendre que garder 6 chiffres significatifs
entre 2 opérandes ayant 6 magnitudes d'écart impose de traiter
ces opérations avec 12 chiffres significatifs.
(7 chiffres significatifs, en fait).
Sauf sumUp et sumDown, je crois que je n'ai jamais de cas où il
y a six magnitudes d'écart.
L'idée derrière sumPriority et sumBinary, en fait, c'est
que les magnitudes soient toujours à peu près identiques
sumPriority le garantit (à un prix exhorbitant),
et sumBinary joue sur la statistique -- c'est
d'ailleurs pourquoi dans mes tests de précision j'ai fait des
tests avec le tableau trié ; c'est le cas le plus défavorable
pour sumBinary (en ce qui concerne la précision).
il vient que (array étant un float[]):
double result = 0.0;
for (size_t i = 0; i < size; ++i)
result += array[i];
est bien l'écriture la plus compacte, simple et intuitive qui
donne le bon résultat.
Il s'avère qu'on ne peut pas garantir non plus qu'elle donne le
bon résultat. Il y a toujours des erreurs d'arrondi. Moins
qu'avec float, mais il y en a quand même.
Dans mes tests, j'utilisais les tableaux avec une taille d'une
million d'éléments. Des tableaux assez petits, en somme, mais
j'ai une vieille machine, pas très rapide, qui sert aussi à
d'autres choses, et je n'ai pas voulu la surcharger trop pendant
trop longtemps. J'ai donc modifié mes tests initiaux pour que
les effets de l'arrondi se fassent sentir sur des tableaux plus
petits. (Il faut dire surtout que j'ai assez peu de mémoire. Et
quand le test se met à pager, ça ralentit tout, beaucoup.)
Et qu'est-ce que tu penses que sumBinary fait ?
PC, Linux, g++ :
Floating point tests:
kanze wrote on 17/05/2006 14:10:On peut le dire, vue qu'il fait que j'ai des résultats
différents selon le niveau d'optimisation, sans changer ni
le compilateur ni le CPU. (Et sans avoir un comportement
indéfini ni non-spécifié.)
on est d'accord pour dire qu'un code différent (y compris
source identique, binaire optimisé différemment) peux donner
des résultats différents.
c'est pourquoi je n'autorise aucune optimisation sur des
routines numériques flottantes - le code à exécuter doit être
le mien et non l'invention du compilo (et ça aide à la
portabilité/constance).
Tu trouves que ce n'est pas étonnant que je n'ai pas obtenu
le même résultat ? J'avais l'impression que tu disais que
les résultats étaient indentiques sur un même processeur.
C'est bien un contre-exemple. (Mais peut-être j'ai mal
compris ce que tu voulais dire.)
je le supposais depuis le début: pour un code entièrement
controlé, pas pour un code où le compilo change la précision
des intermédiaires et l'ordre des évaluations.
Alors, de quoi sers-tu pour comparer, si tu n'utilises ni
les tests d'équalité, ni les tests d'ordonnencement ?
éventuellement ces opérateurs non appliqués au types primitifs
mais surchargés par une classe ayant calculé l'erreur (la
précision c'est du même) de l'insatnce à chaque opération
l'impliquant.
Je pourrais ajouter qu'ici on a un problème où la précision
supplémentaire des valeurs intermédiaire sur l'Intel a un
avantage. Sur le PC Linux, j'ai exactement les mêmes résultats
dans les tests de précision, quelque soit l'algorithme utilisé.
je ne te crois pas ! ;)
plus clairement ils le sont parce que tes appels SumXXX<float>
sont strictement équivalents à des SumXXX<double> parce que tu
as laissé le compilo "optimiser" (faire ce qu'il voulait) et
qu'il ne se gène pas de virer ta variable accumulateur qui ne
lui sert à rien (préférant un registre long du FPU).
si tu fais un run sans aucun optim. tu auras des différences.
Pas vraiement. D'abord, les valeurs flottantes sont, par
défaut, toutes dans l'intervalle 0-1,0. Alors, je sais que
je n'aurais pas de débordement de valeur.
j'ai dit "débordement relatif" - lorsque l'on somme
500 000.
+ .512345
et que le résultat ne peut avoir que 6 chiffres significatifs, il a y
débordement qui donne (visiblement)
500 001.
et un peu mieux en interne (en tout cas sur Intel comme tu l'as déjà
indiqué).
[...] (Autant que je me rappelle, Jean-Marc a
posté l'algorithme sumPriority comme un exemple d'un algorithme
« naïf » dans l'autre sens -- c'est la solution trivialement
évident pour réduire l'erreur au maximum, mais à quel coût !)
il est couteux et "compréhensible" (on "comprend" que c'est la
façon de faire, la façon dont est faite l'erreur et dont il
convient de la corriger).
(Évidemment, dans n'importe quelle utilisation réele,
j'utiliserais double pour l'accumulateur de la somme, et non
float. Ici, le but était en partie de voir le comportement
des algorithmes par rapport aux pertes de précision -- et on
voit plus clairement quand les pertes de précision sont plus
grandes.)
arrêtes d'optimiser, tu les verra encore mieux ;)
comme tu disais,il est important de savoir ce que l'on fait --
et ici il faut comprendre que garder 6 chiffres significatifs
entre 2 opérandes ayant 6 magnitudes d'écart impose de traiter
ces opérations avec 12 chiffres significatifs.
(7 chiffres significatifs, en fait).
non je ne pense pas que 7 suffisent.
et sumBinary joue sur la statistique -- c'est d'ailleurs
pourquoi dans mes tests de précision j'ai fait des tests
avec le tableau trié ; c'est le cas le plus défavorable pour
sumBinary (en ce qui concerne la précision).
yep, il aurait fallu traiter les extrèmes ensemble dans ce cas, en gros:
template< typename T >
T sumBinary( float const* array, size_t size )
{
return array[0] + array[size-1] +
(size == 2) ? 0.0 : sumBinary<T>(array + 1, size - 2);
}
Dans mes tests, j'utilisais les tableaux avec une taille
d'une million d'éléments. Des tableaux assez petits, en
somme, mais j'ai une vieille machine, pas très rapide, qui
sert aussi à d'autres choses, et je n'ai pas voulu la
surcharger trop pendant trop longtemps. J'ai donc modifié
mes tests initiaux pour que les effets de l'arrondi se
fassent sentir sur des tableaux plus petits. (Il faut dire
surtout que j'ai assez peu de mémoire. Et quand le test se
met à pager, ça ralentit tout, beaucoup.)
le tableau ne fait que 4 Mo !?! ... ah oui swapper la
récursivité :)
kanze wrote on 17/05/2006 14:10:
On peut le dire, vue qu'il fait que j'ai des résultats
différents selon le niveau d'optimisation, sans changer ni
le compilateur ni le CPU. (Et sans avoir un comportement
indéfini ni non-spécifié.)
on est d'accord pour dire qu'un code différent (y compris
source identique, binaire optimisé différemment) peux donner
des résultats différents.
c'est pourquoi je n'autorise aucune optimisation sur des
routines numériques flottantes - le code à exécuter doit être
le mien et non l'invention du compilo (et ça aide à la
portabilité/constance).
Tu trouves que ce n'est pas étonnant que je n'ai pas obtenu
le même résultat ? J'avais l'impression que tu disais que
les résultats étaient indentiques sur un même processeur.
C'est bien un contre-exemple. (Mais peut-être j'ai mal
compris ce que tu voulais dire.)
je le supposais depuis le début: pour un code entièrement
controlé, pas pour un code où le compilo change la précision
des intermédiaires et l'ordre des évaluations.
Alors, de quoi sers-tu pour comparer, si tu n'utilises ni
les tests d'équalité, ni les tests d'ordonnencement ?
éventuellement ces opérateurs non appliqués au types primitifs
mais surchargés par une classe ayant calculé l'erreur (la
précision c'est du même) de l'insatnce à chaque opération
l'impliquant.
Je pourrais ajouter qu'ici on a un problème où la précision
supplémentaire des valeurs intermédiaire sur l'Intel a un
avantage. Sur le PC Linux, j'ai exactement les mêmes résultats
dans les tests de précision, quelque soit l'algorithme utilisé.
je ne te crois pas ! ;)
plus clairement ils le sont parce que tes appels SumXXX<float>
sont strictement équivalents à des SumXXX<double> parce que tu
as laissé le compilo "optimiser" (faire ce qu'il voulait) et
qu'il ne se gène pas de virer ta variable accumulateur qui ne
lui sert à rien (préférant un registre long du FPU).
si tu fais un run sans aucun optim. tu auras des différences.
Pas vraiement. D'abord, les valeurs flottantes sont, par
défaut, toutes dans l'intervalle 0-1,0. Alors, je sais que
je n'aurais pas de débordement de valeur.
j'ai dit "débordement relatif" - lorsque l'on somme
500 000.
+ .512345
et que le résultat ne peut avoir que 6 chiffres significatifs, il a y
débordement qui donne (visiblement)
500 001.
et un peu mieux en interne (en tout cas sur Intel comme tu l'as déjà
indiqué).
[...] (Autant que je me rappelle, Jean-Marc a
posté l'algorithme sumPriority comme un exemple d'un algorithme
« naïf » dans l'autre sens -- c'est la solution trivialement
évident pour réduire l'erreur au maximum, mais à quel coût !)
il est couteux et "compréhensible" (on "comprend" que c'est la
façon de faire, la façon dont est faite l'erreur et dont il
convient de la corriger).
(Évidemment, dans n'importe quelle utilisation réele,
j'utiliserais double pour l'accumulateur de la somme, et non
float. Ici, le but était en partie de voir le comportement
des algorithmes par rapport aux pertes de précision -- et on
voit plus clairement quand les pertes de précision sont plus
grandes.)
arrêtes d'optimiser, tu les verra encore mieux ;)
comme tu disais,il est important de savoir ce que l'on fait --
et ici il faut comprendre que garder 6 chiffres significatifs
entre 2 opérandes ayant 6 magnitudes d'écart impose de traiter
ces opérations avec 12 chiffres significatifs.
(7 chiffres significatifs, en fait).
non je ne pense pas que 7 suffisent.
et sumBinary joue sur la statistique -- c'est d'ailleurs
pourquoi dans mes tests de précision j'ai fait des tests
avec le tableau trié ; c'est le cas le plus défavorable pour
sumBinary (en ce qui concerne la précision).
yep, il aurait fallu traiter les extrèmes ensemble dans ce cas, en gros:
template< typename T >
T sumBinary( float const* array, size_t size )
{
return array[0] + array[size-1] +
(size == 2) ? 0.0 : sumBinary<T>(array + 1, size - 2);
}
Dans mes tests, j'utilisais les tableaux avec une taille
d'une million d'éléments. Des tableaux assez petits, en
somme, mais j'ai une vieille machine, pas très rapide, qui
sert aussi à d'autres choses, et je n'ai pas voulu la
surcharger trop pendant trop longtemps. J'ai donc modifié
mes tests initiaux pour que les effets de l'arrondi se
fassent sentir sur des tableaux plus petits. (Il faut dire
surtout que j'ai assez peu de mémoire. Et quand le test se
met à pager, ça ralentit tout, beaucoup.)
le tableau ne fait que 4 Mo !?! ... ah oui swapper la
récursivité :)
kanze wrote on 17/05/2006 14:10:On peut le dire, vue qu'il fait que j'ai des résultats
différents selon le niveau d'optimisation, sans changer ni
le compilateur ni le CPU. (Et sans avoir un comportement
indéfini ni non-spécifié.)
on est d'accord pour dire qu'un code différent (y compris
source identique, binaire optimisé différemment) peux donner
des résultats différents.
c'est pourquoi je n'autorise aucune optimisation sur des
routines numériques flottantes - le code à exécuter doit être
le mien et non l'invention du compilo (et ça aide à la
portabilité/constance).
Tu trouves que ce n'est pas étonnant que je n'ai pas obtenu
le même résultat ? J'avais l'impression que tu disais que
les résultats étaient indentiques sur un même processeur.
C'est bien un contre-exemple. (Mais peut-être j'ai mal
compris ce que tu voulais dire.)
je le supposais depuis le début: pour un code entièrement
controlé, pas pour un code où le compilo change la précision
des intermédiaires et l'ordre des évaluations.
Alors, de quoi sers-tu pour comparer, si tu n'utilises ni
les tests d'équalité, ni les tests d'ordonnencement ?
éventuellement ces opérateurs non appliqués au types primitifs
mais surchargés par une classe ayant calculé l'erreur (la
précision c'est du même) de l'insatnce à chaque opération
l'impliquant.
Je pourrais ajouter qu'ici on a un problème où la précision
supplémentaire des valeurs intermédiaire sur l'Intel a un
avantage. Sur le PC Linux, j'ai exactement les mêmes résultats
dans les tests de précision, quelque soit l'algorithme utilisé.
je ne te crois pas ! ;)
plus clairement ils le sont parce que tes appels SumXXX<float>
sont strictement équivalents à des SumXXX<double> parce que tu
as laissé le compilo "optimiser" (faire ce qu'il voulait) et
qu'il ne se gène pas de virer ta variable accumulateur qui ne
lui sert à rien (préférant un registre long du FPU).
si tu fais un run sans aucun optim. tu auras des différences.
Pas vraiement. D'abord, les valeurs flottantes sont, par
défaut, toutes dans l'intervalle 0-1,0. Alors, je sais que
je n'aurais pas de débordement de valeur.
j'ai dit "débordement relatif" - lorsque l'on somme
500 000.
+ .512345
et que le résultat ne peut avoir que 6 chiffres significatifs, il a y
débordement qui donne (visiblement)
500 001.
et un peu mieux en interne (en tout cas sur Intel comme tu l'as déjà
indiqué).
[...] (Autant que je me rappelle, Jean-Marc a
posté l'algorithme sumPriority comme un exemple d'un algorithme
« naïf » dans l'autre sens -- c'est la solution trivialement
évident pour réduire l'erreur au maximum, mais à quel coût !)
il est couteux et "compréhensible" (on "comprend" que c'est la
façon de faire, la façon dont est faite l'erreur et dont il
convient de la corriger).
(Évidemment, dans n'importe quelle utilisation réele,
j'utiliserais double pour l'accumulateur de la somme, et non
float. Ici, le but était en partie de voir le comportement
des algorithmes par rapport aux pertes de précision -- et on
voit plus clairement quand les pertes de précision sont plus
grandes.)
arrêtes d'optimiser, tu les verra encore mieux ;)
comme tu disais,il est important de savoir ce que l'on fait --
et ici il faut comprendre que garder 6 chiffres significatifs
entre 2 opérandes ayant 6 magnitudes d'écart impose de traiter
ces opérations avec 12 chiffres significatifs.
(7 chiffres significatifs, en fait).
non je ne pense pas que 7 suffisent.
et sumBinary joue sur la statistique -- c'est d'ailleurs
pourquoi dans mes tests de précision j'ai fait des tests
avec le tableau trié ; c'est le cas le plus défavorable pour
sumBinary (en ce qui concerne la précision).
yep, il aurait fallu traiter les extrèmes ensemble dans ce cas, en gros:
template< typename T >
T sumBinary( float const* array, size_t size )
{
return array[0] + array[size-1] +
(size == 2) ? 0.0 : sumBinary<T>(array + 1, size - 2);
}
Dans mes tests, j'utilisais les tableaux avec une taille
d'une million d'éléments. Des tableaux assez petits, en
somme, mais j'ai une vieille machine, pas très rapide, qui
sert aussi à d'autres choses, et je n'ai pas voulu la
surcharger trop pendant trop longtemps. J'ai donc modifié
mes tests initiaux pour que les effets de l'arrondi se
fassent sentir sur des tableaux plus petits. (Il faut dire
surtout que j'ai assez peu de mémoire. Et quand le test se
met à pager, ça ralentit tout, beaucoup.)
le tableau ne fait que 4 Mo !?! ... ah oui swapper la
récursivité :)
une méthode plus générique, ne dépendant pas notamment de la
qualité du randomizer et de l'ordonnencement des valeurs à
sommer mais traitant de valeurs à écart type faible de:
a) calculer la moyenne (ici 0.5)
b) accumuler des valeurs tant que les résultats ne dépasse pas 100 fo is
cette moyenne (pour ne perdre que 2 chiffres significatifs entre ce
résultat et un élément à sommer)
c) stocker cette somme dans un vector intermédiaire
d) boucler pour épuiser les valeurs.
e) considérer les éléments du vecteur de résultats intermédiair es comme
le nouvel array d'input et bouclé en 1.
on aura ainsi un sumBinaryTierce dont le niveau dépend de la
distribution.
une méthode plus générique, ne dépendant pas notamment de la
qualité du randomizer et de l'ordonnencement des valeurs à
sommer mais traitant de valeurs à écart type faible de:
a) calculer la moyenne (ici 0.5)
b) accumuler des valeurs tant que les résultats ne dépasse pas 100 fo is
cette moyenne (pour ne perdre que 2 chiffres significatifs entre ce
résultat et un élément à sommer)
c) stocker cette somme dans un vector intermédiaire
d) boucler pour épuiser les valeurs.
e) considérer les éléments du vecteur de résultats intermédiair es comme
le nouvel array d'input et bouclé en 1.
on aura ainsi un sumBinaryTierce dont le niveau dépend de la
distribution.
une méthode plus générique, ne dépendant pas notamment de la
qualité du randomizer et de l'ordonnencement des valeurs à
sommer mais traitant de valeurs à écart type faible de:
a) calculer la moyenne (ici 0.5)
b) accumuler des valeurs tant que les résultats ne dépasse pas 100 fo is
cette moyenne (pour ne perdre que 2 chiffres significatifs entre ce
résultat et un élément à sommer)
c) stocker cette somme dans un vector intermédiaire
d) boucler pour épuiser les valeurs.
e) considérer les éléments du vecteur de résultats intermédiair es comme
le nouvel array d'input et bouclé en 1.
on aura ainsi un sumBinaryTierce dont le niveau dépend de la
distribution.
Mais ce n'était qu'une remarque parenthètique, parce que ça ne
change rien au problème. Grosso modo, si je comprends ce que tu
veux dire, c'est que si j'ajoute deux nombres de 6 chiffres, il
faut que j'utilise l'arithmétique à 12 chiffres, et que je garde
le résultat sur 12 chiffres (minimum, évidemment). Que la
précision nécessaire est la somme des précisions en entrée.
Mais ce n'était qu'une remarque parenthètique, parce que ça ne
change rien au problème. Grosso modo, si je comprends ce que tu
veux dire, c'est que si j'ajoute deux nombres de 6 chiffres, il
faut que j'utilise l'arithmétique à 12 chiffres, et que je garde
le résultat sur 12 chiffres (minimum, évidemment). Que la
précision nécessaire est la somme des précisions en entrée.
Mais ce n'était qu'une remarque parenthètique, parce que ça ne
change rien au problème. Grosso modo, si je comprends ce que tu
veux dire, c'est que si j'ajoute deux nombres de 6 chiffres, il
faut que j'utilise l'arithmétique à 12 chiffres, et que je garde
le résultat sur 12 chiffres (minimum, évidemment). Que la
précision nécessaire est la somme des précisions en entrée.
Du coup, je ne comprends pas pourquoi il faudrait particulièrement 12
chiffres significatifs pour additionner deux nombres ayant 6 chiffres
significatifs.
excès de "normalisme" ? ;) - le point n'étant pas ce que garantit la
Du coup, je ne comprends pas pourquoi il faudrait particulièrement 12
chiffres significatifs pour additionner deux nombres ayant 6 chiffres
significatifs.
excès de "normalisme" ? ;) - le point n'étant pas ce que garantit la
Du coup, je ne comprends pas pourquoi il faudrait particulièrement 12
chiffres significatifs pour additionner deux nombres ayant 6 chiffres
significatifs.
excès de "normalisme" ? ;) - le point n'étant pas ce que garantit la