Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Fortran & unpack

20 réponses
Avatar
Julien K.
Bonjour,

j'écris un script de lecture de données binaires produites par un code de
calcul en F77; une valeur de charge particulière est affectée aux mailles
inactives, typiqument 999.99. Les valeurs sont des REALs écrits en binaire
non formatté.

Malheureusement dans la lecture du fichier je ne récupère pas exactement
cette valeur, je suis confronté à un problème de ce genre:

$ perl -e 'print unpack("f",(pack("f",999.99))) ;'
999.989990234375

$ perl -e 'print sprintf("%.4f",unpack("f",(pack("f",999.99)))) ;'
999.9900

Comment puis-je faire pour récupérer 999.99, valeur utilisée dans des
tests? Utiliser sprintf résoud le problème mais ça ne me semble pas /propre/.

Merci de vos lumières, docs...

Julien

10 réponses

1 2
Avatar
Paul Gaborit
À (at) Wed, 30 Jan 2008 15:07:17 +0100,
"Julien K." écrivait (wrote):
On 30-01-2008, Paul Gaborit wrote:
[...]

Avec la première version de cette ligne, les valeurs *flottantes* envoyées
dans @{ $t } sont exactement les mêmes que celles écrites par Fortran dans
le fichier binaire... mais comme Perl n'utilise que des doubles (en
interne), tu récupères nécessairement plus de précision qu'en Fortan.


Précision au sens "nombre de chiffre" je suppose (pas au sens de "se
rapproche de la valeur exacte"). Donc ce ne sont pas "exactement les mêmes
que celles [du] fichier binaire" puisque lorsque je veux les vérifier elles
sont différentes; j'ajoute même que lorsque (sans le sprintf) je teste la
présence de '999.99' il n'y a aucune valeur qui corresponde.


La notion de "valeur exacte" n'a pas de sens puisque j'imagine que
cette valeur provient d'un calcul en Fortran. La machine a bien dû
tronqué cette valeur pour la faire tenir dans le nombre de bits
autorisés dans un float. C'est donc déjà une valeur approchée.

La valeur 999.99 n'est pas représentable en binaire que ce soit en
float ou en double (c'est une valeur avec une infinité de
décimales...). Ce n'est donc qu'une valeur approchée qui stockée dans
un float ou un double. Mais comment savoir la valeur réellement
utilisée ? Ce n'est pas facile... mais en tous cas, ce n'est pas en
l'affichant en décimale (via un print quelconque) puisque cette
conversion implique elle aussi des arrondis.

Aucun langage ne garantit qu'un passage binaire->décimal->binaire
redonne toujours la même valeur. Aucun langage ne garantit qu'un
passage float>doulbe->float redonne toujours la même valeur.

De plus lorsqu'on travaille sur des nombres à virgule flottante, on ne
teste jamais l'égalité stricte. On utilise toujours une égalité à
espilon près...

Dois-je à tn avis effectuer les tests en binaire avec ce que renvoie
pack('f',999.99)?


Tu peux peut-être tenter d'utiliser la valeur numérique Perl que tu
récupères via :

my $val = unpack('f', pack('f', 999.99));

Mais rien ne garantit que ça marche.

Mais même en Fortran (ou en C ou en C++), rien ne te garantit qu'une
valeur qui (une fois convertie en texte) s'affiche sous la forme
999.99 sera réellement égale à 999.99 !

C'est d'ailleurs signalé dans la doc de 'pack'. Petit extrait (en
français) :

Sachez que Perl utilise des doubles en interne pour tous les
calculs numériques et qu'une conversion de double vers float puis
retour vers double amène à une perte de précision (i.e.,
unpack("f", pack("f", $foo)) est généralement différent de $foo).


Cette dernière phrase décrit exactement la situation que je rencontre,
je l'avais loupée.


Tu peux le vérifier en Fortran avec le code suivant (formatté à la
fortran 90) :

----------------------------------------
program test

real(4) a
real(8) c

a = 999.99
c = a
write (*,*) a, c
end program test
----------------------------------------

qui affiche :

999.9900 999.989990234375

La seconde valeur est exactement celle qu'on obtient en Perl via :

% perl -e 'print unpack('f', pack('f', 999.99)), "n"'
999.989990234375

Il y a donc totale cohérence entre Perl et Fortran sur le coup. Ton
seul problème est que Perl (et le processeur) ne manipule jamais des
floats en interne alors que Fortran accepte encore de le faire (juste
pour le stockage pas pour le calcul).

Utiliser le module PDL arrangerait-il les choses?


Je n'ai jamais utiliser PDL...

Je commence à croire que la modification du code pour tout passer en
DOUBLE dans le source Fortran serait la meilleure solution à moyen
terme.


Ça, c'est quasi certain (sauf cas très très particuliers).

Je vais finir par recoder tout ça en C++, je sens que ça va pas tarder.


Ou en Fortran... ;-)

--
Paul Gaborit - <http://perso.enstimac.fr/~gaborit/>
Perl en français - <http://perl.enstimac.fr/>


Avatar
Thierry B.
--{ Julien K. a plopé ceci: }--


Je vais finir par recoder tout ça en C++, je sens que ça va pas tarder.

{troll} quand on pense avoir UN problème, on recode en C++.

Après, on a plein de problèmes, mais cépadnotfot. {/troll}.


--
awk 'ORS=NR%10?"n":"nn"'

Avatar
Julien K.
On 30-01-2008, Paul Gaborit wrote:

À (at) Wed, 30 Jan 2008 15:07:17 +0100,
"Julien K." écrivait (wrote):
On 30-01-2008, Paul Gaborit wrote:
[...]

Avec la première version de cette ligne, les valeurs *flottantes*
envoyées dans @{ $t } sont exactement les mêmes que celles écrites par
Fortran dans le fichier binaire... mais comme Perl n'utilise que des
doubles (en interne), tu récupères nécessairement plus de précision
qu'en Fortan.


Précision au sens "nombre de chiffre" je suppose (pas au sens de "se
rapproche de la valeur exacte"). Donc ce ne sont pas "exactement les
mêmes que celles [du] fichier binaire" puisque lorsque je veux les
vérifier elles sont différentes; j'ajoute même que lorsque (sans le
sprintf) je teste la présence de '999.99' il n'y a aucune valeur qui
corresponde.


La notion de "valeur exacte" n'a pas de sens puisque j'imagine que cette
valeur provient d'un calcul en Fortran.


Non justement, il s'agit d'une valeur entrée par l'utilisateur pour
indiquer une maille inactive (cf mon premier post); sans cela je n'aurais
jamais eu l'idée de tester une égalité stricte, je connais deux-trois
choses sur la manipulation des décimaux quand-même ;-)

J'aurais très bien pu choisir un entier à la place, par exemple The Number
Of The Beast (666). D'ailleurs:

$ perl -e 'print unpack("f",(pack("f",666))) ;'
666

Merci pour l'explication.

Julien

--
Comme quoi Aleister Crowley...



Avatar
Paul Gaborit
À (at) Wed, 30 Jan 2008 19:00:01 +0100,
"Thierry B." écrivait (wrote):
--{ Julien K. a plopé ceci: }--


Je vais finir par recoder tout ça en C++, je sens que ça va pas tarder.

{troll} quand on pense avoir UN problème, on recode en C++.

Après, on a plein de problèmes, mais cépadnotfot. {/troll}.


{troll et demi} D'un autre côté, c'est souvent parce qu'on a codé en
C, en Fortran, en C++, en Perl... qu'on arrive à trouver les solutions
aux problèmes qu'on rencontre. {/troll et demi}

--
Paul Gaborit - <http://perso.enstimac.fr/~gaborit/>
Perl en français - <http://perl.enstimac.fr/>


Avatar
Paul Gaborit
À (at) Wed, 30 Jan 2008 19:04:20 +0100,
"Julien K." écrivait (wrote):
[...]
Non justement, il s'agit d'une valeur entrée par l'utilisateur pour
indiquer une maille inactive (cf mon premier post); sans cela je n'aurais
jamais eu l'idée de tester une égalité stricte, je connais deux-trois
choses sur la manipulation des décimaux quand-même ;-)


C'est donc un marqueur... Il suffit donc d'appliquer la formule à la
valeur de ce marqueur avant de pouvoir le comparer par inégalité
stricte aux valeurs stockées dans le fichier binaire :

$marqueur = unpack('f',(pack('f', 999.99)));

J'aurais très bien pu choisir un entier à la place, par exemple The Number
Of The Beast (666). D'ailleurs:

$ perl -e 'print unpack("f",(pack("f",666))) ;'
666


;-) Perl, c'est l'enfer !

--
Paul Gaborit - <http://perso.enstimac.fr/~gaborit/>
Perl en français - <http://perl.enstimac.fr/>

Avatar
Yves Pointin
On 30-01-2008, Paul Gaborit wrote:
À (at) Wed, 30 Jan 2008 15:07:17 +0100,
"Julien K." écrivait (wrote):



Non justement, il s'agit d'une valeur entrée par l'utilisateur pour
indiquer une maille inactive (cf mon premier post); sans cela je n'aurais
jamais eu l'idée de tester une égalité stricte, je connais deux-trois
choses sur la manipulation des décimaux quand-même ;-)

J'aurais très bien pu choisir un entier à la place, par exemple The Number
Of The Beast (666). D'ailleurs:

$ perl -e 'print unpack("f",(pack("f",666))) ;'
666



ou faire un test
if(abs($xx+999.99) < 0.0001) {

idem en FORTRAN où le test d'égalité à un réel n'est jamais garanti.


Cordialement,
--
Dr. POINTIN Yves B.
perl -e "$_='';1 while
s/(.{3})(.{3})?/$_{$2}=$1,$2/e; ; print while $_=$_{$_};"


Avatar
Julien K.
On 30-01-2008, Thierry B. wrote:
--{ Julien K. a plopé ceci: }--

Je vais finir par recoder tout ça en C++, je sens que ça va pas tarder.

{troll} quand on pense avoir UN problème, on recode en C++.

Après, on a plein de problèmes, mais cépadnotfot. {/troll}.

Normalement on dit ça à propos de sed, d'ailleurs dans le Unix Haters'

Handbook:

"Some people, when confronted with a Unix problem, think 'I know,
I'll use sed...' Now they have two problems..."

Plus sérieusement ça me permettrait d'utiliser Vtk (www.vtk.org), en ce
moment j'utilise divers moyens détournés (Matlab, exlport vers SIG) pour
visualiser des données; il existe des bindings pour Python et Tcl, il en
existait pour Perl mais j'ai l'impression que c'est mort.

<troll> Je vais quand même pas me mettre à faire du Python </>

Julien


Avatar
Julien K.
On 30-01-2008, Paul Gaborit wrote:

À (at) Wed, 30 Jan 2008 19:04:20 +0100,
"Julien K." écrivait (wrote):
[...]
Non justement, il s'agit d'une valeur entrée par l'utilisateur pour
indiquer une maille inactive (cf mon premier post); sans cela je n'aurais
jamais eu l'idée de tester une égalité stricte, je connais deux-trois
choses sur la manipulation des décimaux quand-même ;-)


C'est donc un marqueur... Il suffit donc d'appliquer la formule à la
valeur de ce marqueur avant de pouvoir le comparer par inégalité
stricte aux valeurs stockées dans le fichier binaire :

$marqueur = unpack('f',(pack('f', 999.99)));


Je vais voir ce qui est le plus commode, merci pour tes conseils.

Julien


Avatar
Julien K.
On 31-01-2008, Yves Pointin wrote:
On 30-01-2008, Paul Gaborit wrote:
À (at) Wed, 30 Jan 2008 15:07:17 +0100,
"Julien K." écrivait (wrote):


Non justement, il s'agit d'une valeur entrée par l'utilisateur pour
indiquer une maille inactive (cf mon premier post); sans cela je n'aurais
jamais eu l'idée de tester une égalité stricte, je connais deux-trois
choses sur la manipulation des décimaux quand-même ;-)

J'aurais très bien pu choisir un entier à la place, par exemple The Number
Of The Beast (666). D'ailleurs:

$ perl -e 'print unpack("f",(pack("f",666))) ;'
666



ou faire un test
if(abs($xx+999.99) < 0.0001) {


Plutôt:

if(abs($xx-999.99) < 0.0001) {

non? Ça rajoute une opération mais bon, je vais voir. Merci.

Julien



Avatar
Yves Pointin
On 31-01-2008, Yves Pointin wrote:
.....

ou faire un test
if(abs($xx+999.99) < 0.0001) {


Plutôt:

if(abs($xx-999.99) < 0.0001) {

non? Ça rajoute une opération mais bon, je vais voir. Merci.

Julien


Oui, désolé, mais c'est parce que j'utilise beaucoup -999. comme valeur
absente !

--
Dr. POINTIN Yves B.
perl -e "$_='';1 while
s/(.{3})(.{3})?/$_{$2}=$1,$2/e; ; print while $_=$_{$_};"


1 2