[ZX SPECTRUM] Virgule flottante... je nage! (codes C++)

Cette catégorie traite de développements récents pour nos vieilles machines, applications, jeux ou démos... Amis programmeurs, c'est ici que vous pourrez enfin devenir célèbres!

Modérateurs : Papy.G, fneck, Carl

Xavier_AL

[ZX SPECTRUM] Virgule flottante... je nage! (codes C++)

Message par Xavier_AL »

Salut,

Je cherche un module C++ qui pourrait faire la conversion FP 5 bytes (Spectrum/ZX81) vers des valeurs DOUBLE.
J'ai trouvé ça, mais j'avoue ne pas tout comprendre.
Comme je ne suis pas très Matheux, je suis dans l'impasse, même si le projet fonctionne...

C'est pour finir un projet 'tap/tzx' vers texte qui n'attend que cette routine....

Mon code (honteusement chouré à David Gonzales), qui me donne des résultats erronés:

Code : Tout sélectionner

double FloatToDec(unsigned char *ZxFP)
{   
/// Code from David Gonzales

// input is the 5 bytes that comprise the floating point number
    unsigned char x[6];
    double DecNumber = 0;
    int f,t;
    unsigned char  Xdigits;
    int ZxDigits;
    bool ZxSign;

    
    		//	printf("%d %d %d %d %d",ZxFP[0],ZxFP[1],ZxFP[2],ZxFP[3],ZxFP[4]);
    			
  if(memcmp(ZxFP,"\000\000\000\000\000",5) == 0) 
    {
        return 0;
    }

    //'get sign from second byte
    ZxSign = ZxFP[1] & 128;
    

   		
    //'Adjust second byte after getting sign
     ZxFP[1] = ZxFP[1] | 128;

    //drop trailing BYTES that equal 0
    //'result in x$
    for( f = 4; f >= 1; f--)
    {
        if(ZxFP[f] != 0) break;
    }
    
    int len_x = f;
    memcpy(x,ZxFP+1, len_x);
	//x[len_x]=0;

    //'get # of binary digits needed from first byte
    ZxDigits = ZxFP[0] - 128;
    		
    // drop trailing BITS that equal 0
    unsigned char  bb = x[len_x - 1];
    for(f = 6; f >= 1 ; f--)	
    {
        if(bb & 1)   break;
        bb = bb >> 1;
	}
    DecNumber = bb;
         			
    //f now contains the number of significant bits
	
    //'get # of binary digits in base number
    Xdigits = ((unsigned char)len_x - 1) * 8 + (unsigned char)f;

    //'convert string to base number
    t = 0; //   'counter for powers of figure (256)
double   figure = pow((double)2, f);
    for(f = len_x - 1; f >= 1; f--)
    {
        DecNumber = DecNumber + (x[f-1] * figure * pow((double)256,t));
        t = t + 1;
    }
    
    //'based on exponent, modify number to correct number
    //'of digits
    DecNumber = DecNumber * pow((double)2,ZxDigits - Xdigits);
           			printf("bb(%d) :\r\n",DecNumber);
    if (ZxSign) DecNumber = -DecNumber;
    
    return DecNumber;
}
Le code sera intégré à une mise à jour du code de Chris Cowley (SnaList):
- support TZX/TAP.
- extraction du code en mode texte pour le basic, listings pour l'assembleur et traitement des arrays (char et num )
Le code des FP sert à extraire les valeurs 5bytes des tableaux numériques.

Infos: https://www.sinclairzxworld.com/viewtop ... f=6&t=1422
Dernière modification par Xavier_AL le 17 avr. 2018 00:51, modifié 3 fois.
Xavier_AL

Re: [ZX SPECTRUM] Virgule flottante... je nage!

Message par Xavier_AL »

Ha!...
Oui, le codage pour CPC est presque le même, sauf que les octets sont dans l'ordre inverse (4 derniers octets).
Et c'est là que je me dis que je peux prendre une code C++ pour Amstrad, mais, ce serai trop facile.
:mrgreen:
__sam__
Messages : 7923
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [ZX SPECTRUM] Virgule flottante... je nage!

Message par __sam__ »

Le format 5 octet n'est pas un format standard (IEEE). C'est quoi la spec du format 5 octets "spectrum" ?

A partir de la lecture du code C, je déduis:
  • octet 0 : exposant + 129
  • octet 1 : bit7 = signe, reste = mantisse (big endian)
  • 2 ..4 : mantisse (big endian).
est-ce ca ?

Si c'est le cas je décoderais comme suit (en supposant que la mantisse est en big-endian tant au niveau des octets que des bits dans les octets:

Code : Tout sélectionner

double convert(unsigned char *input) {
	double mantissa = 0;
	double coef = (input[1]&128) ? -1 : 1;
	int mask = 128, j=1;

	if(input[0]==0) return 0; // en IEEE l'exposant 0 correspond à la valeur 0.

	input[1] |= mask; // attention, on modifie l'entrée!!!
	do {
		if(input[j] & mask) mantissa += coef;
		coef /= 2; mask >>= 1;
		if(mask==0) {mask=128;++j;}
	} while(j<5);
	return mantissa*pow(2, input[0]-129);
}
Dernière modification par __sam__ le 16 avr. 2018 00:52, modifié 5 fois.
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
Xavier_AL

Re: [ZX SPECTRUM] Virgule flottante... je nage!

Message par Xavier_AL »

:oops:

[EXP] [MANTISSA]
129 ,0,0,0,0
=1
La puissance sur l'octet 1
Le signe sur l'octet 2 (bit fort)
Le reste c'est ... la mantissa pour le EXP.


Image

Dans le code, je ne comprend pas les rotations binaire sur la mantissa!
(tout le début c'est ok, signe, exposant... mais, après comprends pu!)

La routine réciproque:

Code : Tout sélectionner

//Part of :
/* zmakebas - convert a text file containing a speccy Basic program
 *            into an actual program file loadable on a speccy.
 *
 * Public domain by Russell Marks, 1998.
 */

/* dbl2spec() converts a double to an inline-basic-style speccy FP number.
 *
 * usage: dbl2spec(num,&exp,&man);
 *
 * num is double to convert.
 * pexp is an int * to where to return the exponent byte.
 * pman is an unsigned long * to where to return the 4 mantissa bytes.
 * bit 31 is bit 7 of the 0th (first) mantissa byte, and bit 0 is bit 0 of
 * the 3rd (last). As such, the unsigned long returned *must* be written
 * to whatever file in big-endian format to make any sense to a speccy.
 *
 * returns 1 if ok, 0 if exponent too big.
 */
int dbl2spec(double num,int *pexp,unsigned long *pman)
{
int exp;
unsigned long man;

/* check for small integers */
if(num==(long)num && num>=-65535.0 && num<=65535.0)
  {
  /* ignores sign - see below, which applies to ints too. */
  long tmp=(long)fabs(num);
  
  exp=0;
  man=((tmp%256)<<16)|((tmp>>8)<<8);
  }
else
  {
  int f;
  
  /* It appears that the sign bit is always left as 0 when floating-point
   * numbers are embedded in programs, and the speccy appears to use the
   * '-' character to detemine negativity - tests confirm this.
   * As such, we *completely ignore* the sign of the number.
   * exp is 0x80+exponent.
   */
  num=fabs(num);
  
  /* binary standard form goes from 0.50000... to 0.9999...(dec), like
   * decimal which goes from        0.10000... to 0.9999....
   */
  
  /* as such, if the number is >=1, it gets divided by 2, and exp++.
   * And if the number is <0.5, it gets multiplied by 2, and exp--.
   */
  exp=0;
  while(num>=1.0)
    {
    num/=2.0; exp++;
    }

  while(num<0.5)
    {
    num*=2.0; exp--;
    }

  /* so now the number is in binary standard form in exp and num.
   * we check the range of exp... -128 <= exp <= 127.
   * (if outside, we return error (i.e. 0))
   */
   
  if(exp<-128 || exp>127) return(0);
    
  exp=128+exp;
  
  /* so now all we need to do is roll the bits off the mantissa in `num'.
   * we start at the 0.5ths bit at bit 0, and shift left 1 each time
   * round the loop.
   */
  
  num*=2.0;  /* make it so that the 0.5ths bit is the integer part, */
	     /* and the rest is the fractional (0.xxx) part. */
  
  man=0;
  for(f=0;f<32;f++)
    {
    man<<=1;
    man|=(int)num;
    num-=(int)num;
    num*=2.0;
    }

  /* Now, if (int)num is non-zero (well, 1) then we should generally
   * round up 1. We don't do this if it would cause an overflow in the
   * mantissa, though.
   */
   
  if((int)num && man!=0xFFFFFFFF) man++;
    
  /* finally, zero out the top bit */
  man&=0x7FFFFFFF;
  }

/* done */
*pexp=exp; *pman=man;
return(1);
}
Dernière modification par Xavier_AL le 18 avr. 2018 03:31, modifié 2 fois.
__sam__
Messages : 7923
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [ZX SPECTRUM] Virgule flottante... je nage!

Message par __sam__ »

ben pour comprendre les rotation binaire. Tu as mask qui décrit toutes les puissances décroissantes de 2: 128, 64, 32, 16, 8, 4, 2 , 1. Ensuite il passe à 0, et le test sur mask le fait repasser à 128 et incrémente j. Le couple (mask, j) parcourt donc tous les 32 bits de la mantisse. Le test (input[j] & mask) vérifie si le bit correspondant de la mantisse est à 1. Si c'est le cas, on ajoute le coef courant. Puis ce dernier est divisé de moitié.

Il faut "voir" que la mantisse c'est une écriture binaire avec des divisions par 2 au lieu de multiplications: mantisse = somme( (1/2)^i * bit(i)). Bit(i) est donné par input[j]&mask, et dans la somme on ajoute coef si bit(i) vaut 1. Sinon on ajoute rien (ce qui revient à ajouter 0). Quant à coef, il démarre à 1 (ou -1 pour les nb négatifs) et est divisé par 2 à chaque tour. Il vaut donc bien (1/2)^i (^ est la notation puissance).

C'est pas l'implémentation la plus rapide de la planète, mais je pense qu'elle marche. Si on a vraiment besoin d'aller vite, alors on peut réduire le code à:

Code : Tout sélectionner

double decode(unsigned char fp[5]) { // le "unsigned" est important!
	double mantisse;

	if(fp[0]==0) return 0;	
	
	mantisse = (((fp[4]/256.0 + fp[3])/256.0 + fp[2])/256.0 + fp[1])/256.0;
	if(mantisse < 0.5) mantisse += 0.5;  // nombre positif
	else mantisse = -mantisse; // nombre negatif
	
	return mantisse*pow(2, fp[0]-128);
}
Dernière modification par __sam__ le 16 avr. 2018 00:57, modifié 2 fois.
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
Xavier_AL

Re: [ZX SPECTRUM] Virgule flottante... je nage!

Message par Xavier_AL »

Ha!
Ok, Samuel!
Donc, la valeur est encodée sur (4*8)= 32bits - le signe=>31bits, et par roulement, on ajoute les 1/2 valeurs de l'exponentielle.
On prend l'exemple 1.
129,0,0,0,0
Donc, signe +
Exposant 129-128=1
(1/2)^1*exp(0)= 1
8)

(Huf,huf,huf,huf...Hyperventilation!)

Je regarde ton code !!!
__sam__
Messages : 7923
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [ZX SPECTRUM] Virgule flottante... je nage!

Message par __sam__ »

Oui voilà c'est ca! (mais exp(0) c'est 2^0, pas 2.71^0 donc pas exp, mais 2 à la puissance 0) C'est pas très compliqué finalement. Il faut juste voir que comme la mantisse représente toujours un nb entre 1 (inclus) et 2 (exclus), ca veut dire que le bit de poids fort est toujours à 1, et que du coup on peut ne pas le coder ce qui libère un bit pour le signe. Bon je dis entre 1 et 2, mais si on divise tout par deux ca veut dire entre 0.5 et 1.

Dans ma 2e version "optimisée", on voit bien le truc. Si la mantisse calculée est <0.5, c'est que le bit de poids fort est à 0 (et donc on est forcément <0.5 lors de la dernière division par 256.0), le nb est positif et donc on ajoute 0.5 ce qui correspond à corriger le bit absent. Sinon la mantisse est >=0.5 ce qui signifie que le bit de poids fort est (déjà) à 1 et le nb est négatif. On lui change juste alors le signe. C'est une belle optimisation, mais le format IEEE est vraiment bien foutu pour optimiser pleins de trucs.
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
Xavier_AL

Re: [ZX SPECTRUM] Virgule flottante... je nage!

Message par Xavier_AL »

Salut Samuel,
ça me donne toujours des valeurs "extraordinaires"...
donc, j'ai testé en petits-indiens...
whatever file in big-endian format to make any sense to a speccy.
Mais, fp[1],fp[2],fp[3],fp[4] en pt'indiens ça ne donne pas fp[4],fp[3],fp[2],fp[1], car en paquet de 8 bits.
Donc, je comprend mieux la rotation au niveau bytes de la version initiale...
Je testerai l'inversion plus tard...

Mais, ton code a de très fortes chances de fonctionner sur Amstrad CPC... qui est en little-endian.
Ce qui me chagrine c'est ... et je fait une fixation ... la valeur 1.

Avec 0 partout en Mantissa, et le résultat me donne 0 avec un EXP à 1 en fp[0]!!!!
Soit, j'ai un décalage en lecture dans mes fichiers tzx/tap, soit un problème de déclaration de variables dans mon programme C++.

Donc, je dois encore valider la cohérence du code...
Merci beaucoup pour ces explications Samuel!
Avatar de l’utilisateur
hlide
Messages : 3469
Inscription : 29 nov. 2017 10:23

Re: [ZX SPECTRUM] Virgule flottante... je nage!

Message par hlide »

le code me paraît bien alambiqué mais je suppose que c'est dû à la traduction en C du code d'origine écrit en BASIC.

De ce que j'ai compris du format (B0B1B2B3B4), le premier octet encode l'exposant 2^(B0-129), les autres encodent la mantisse sauf le bit 7 de l'octet B1 qui encode le signe. Donc on a :

Code : Tout sélectionner

unsigned char b[5];
double resultat = 0.;
...
if (b[0] || b[1] || b[2] || b[3] || b[4])
{  
    unsigned long long signe = ( b[1] & 128 ) ? 1ull : 0ull;
    unsigned long long exposant = (1ull * b[0] - 129ull  + 1023ull ) & 2047ull;
    unsigned long long mantisse = (1ull * ( *( (unsigned int *)(b + 1) ) ) ) & 0x7FFFFFFF;
    unsigned long long value = (signe << 63) | (exposant << 52) | (mantisse << 21);
    resultat = *((double*)&value);
}
Je n'ai pas vérifié et j'ai peut être fait des erreurs mais l'idée est là je pense - adapter l'exposant et la mantisse à celui du double. Je n'ai pas l'impression qu'il y ait des NANs à gérer.
Xavier_AL

Re: [ZX SPECTRUM] Virgule flottante... je nage!

Message par Xavier_AL »

:?

Il semble y avoir plusieurs encodages pour les entiers et les nombres décimaux.

En basic, nous avons des entiers...
Capture.JPG
Capture.JPG (58.71 Kio) Consulté 4008 fois
En stockage de données...
Capture1.JPG
Capture1.JPG (43.54 Kio) Consulté 4008 fois
Dans un cas on a 1=00;00;01;00;00 et dans l'autre 100/100=81;00 ;00 ;00 ;00
:mrgreen:
Avatar de l’utilisateur
hlide
Messages : 3469
Inscription : 29 nov. 2017 10:23

Re: [ZX SPECTRUM] Virgule flottante... je nage!

Message par hlide »

Ne pas oublier que ces nombres n'encodent pas le premier bit à 1 de la mantisse.
Xavier_AL

Re: [ZX SPECTRUM] Virgule flottante... je nage!

Message par Xavier_AL »

Bon, pour le basic, facile de comprendre que seul les 3 derniers octets sont utilisés... 01 00 00 => 00 00 01
Il aurai été plus simple de décoder les entiers !!! (tag: E0)
:oops:

@hlide: il faut remplacer "ull" par ??? ... on peut les effacer en fait!
Avatar de l’utilisateur
hlide
Messages : 3469
Inscription : 29 nov. 2017 10:23

Re: [ZX SPECTRUM] Virgule flottante... je nage!

Message par hlide »

Xavier_AL a écrit : 16 avr. 2018 02:27 @hlide: il faut remplacer "ull" par ??? ... on peut les effacer en fait!
ull c'est pour dire que l'on veut des constantes en 64-bit (unsigned long long). Bref, je préfère les mettre pour être sûr de ne pas travailler avec des valeurs tronquées à 32-bit.

Et "1ull * x" équivaut à "(unsigned long long)x".
Xavier_AL

Re: [ZX SPECTRUM] Virgule flottante... je nage!

Message par Xavier_AL »

hlide a écrit : 16 avr. 2018 02:31 ull c'est pour dire que l'on veut des constantes en 64-bit (unsigned long long). Bref, je préfère les mettre pour être sûr de ne pas travailler avec des valeurs tronquées à 32-bit.
:shock:
On en apprend tous les jours!
Merci Hlide, pour l'astuce.
__sam__
Messages : 7923
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [ZX SPECTRUM] Virgule flottante... je nage!

Message par __sam__ »

Attends, tu dis que cet encodage flottant encode en plus des entiers pas flottants du tout (donc sans les histoires de mantisse/exposant) ?

Donc en fait le format serait mixte: B0 B1 B2 B3 B4
  • B0=0, ca encode l'entier little endian

    Code : Tout sélectionner

    B2+B3*256
  • B0!=0 ca encode le réel:

    Code : Tout sélectionner

    (B1&128 ? -1 : 1) * ... #signe
    ( (B1|128)/256.0 + B2/256.0^2 + B3/256.0^3 + B4/256.0^4 ) * ...  #mantisse
    2.0^(B0-128) #exposant
Dernière modification par __sam__ le 16 avr. 2018 09:37, modifié 8 fois.
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
Répondre