[vb6] Conversion d'un nombre en "floating point".

Résolu
JeuDuTaquin Messages postés 249 Date d'inscription mardi 4 juillet 2017 Statut Membre Dernière intervention 31 mai 2023 - Modifié le 16 août 2019 à 23:37
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 - 18 août 2019 à 22:38
Salut à tous,

Un vieux serpent de mer m'oblige à réexaminer un ancien projet.
Il me faut convertir un nombre entier ou à virgule, en tockens à virgule flottante compatible ZX81 ou Spectrum.
Mon code fonctionne, mais pas avec certaines valeurs significatives (.5, 128...)… Je peux vivre avec, mais pas sans avoir perdu quelques cheveux.
Donc, en C++ on a ça: (code fonctionnel)…
/* 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);
}
 


Et en VB, après de nombreux calages aproximatifs…

' ZX81 floating point number.
Type Zx81_Long
        Exp As Integer ' - Exponent
        man As String ' - Mantissa
End Type

Const NONESSENTIAL = &H0
Const MAX_PATH = 145
Const OFFSET_4 = 4294967296#
Const MAXINT_4 = 2147483647
Const OFFSET_2 = 65536
Const MAXINT_2 = 32767

Function dbl2zx81(Num As Single) As Zx81_Long
 'On Error Resume Next
 
' * Converts a double to an inline-basic-style ZX81 floating point number.
' *
' * IN:      num  - Number to convert (dbl)
' * OUT:     &exp - Exponent (integer)
' * OUT:     &man - Mantissa (char)
' *

Dim IntNum As Double
Dim tmpint1 As Double
Dim tmpint2 As Double
Dim TmpVal As String
Dim Expon As Double

'    /* Special case for zero */
    If Num = 0 Then
    dbl2zx81.Exp = 0#
    dbl2zx81.man = 0#
     Exit Function
    End If
      
'    /* If negative work with the absolute value */
    If Num < 0 Then Num = -Num

dbl2zx81.Exp = Int(Log(Num) / Log(2))


If dbl2zx81.Exp < -129 Or dbl2zx81.Exp > 126 Then Exit Function

IntNum = ((Num / (2# ^ (dbl2zx81.Exp + Expon#))) * 2147483648#)

' 65536=&h10000
' 32767=&h7FFF

tmpint2 = IntNum - (Fix(IntNum / 65536#) * 65536#) - 28
tmpint1 = (IntNum / 65536#) And 32767#

If tmpint2 < 0 Then tmpint2 = 0

TmpVal = Hex16(UnsignedToLong(tmpint1)) & Hex16(UnsignedToLong(tmpint2))
    
    dbl2zx81.man = TmpVal
    dbl2zx81.Exp = dbl2zx81.Exp + 129#
    Exit Function

Dbl_ERR:
    Disp_Err 11, Str(Num)
End Function

Public Function UnsignedToLong(Value As Double) As Long
    If Value < 0 Or Value >= OFFSET_4 Then Error 6
    
    If Value <= MAXINT_4 Then
        UnsignedToLong = Value
    Else
        UnsignedToLong = Value - OFFSET_4
    End If
End Function

Function Hex16(Value As Long) As String
If Value >= &H10000 Then Hex16 = "FFFF": Exit Function
If Value < &H10 Then
   Hex16 = "000" & Hex(Value): Exit Function
ElseIf Value < &H100 Then
   Hex16 = "00" & Hex(Value): Exit Function
ElseIf Value < &H1000 Then
   Hex16 = "0" & Hex(Value): Exit Function
End If
Hex16 = Hex8(Value \ &H100) & Hex8(Value - (Value \ &H100) * &H100)
End Function


Dans toute cette confusion, j'ai pensé faire une DLL, utilisable en VB6, mais la simplification du code VB peut peut-être clarifier ces erreurs de conversion.

Je serai comblé si un oeil expert pouvait trouver l'erreur qui est à l'origine de ce bogue, probablement imputable au choix des types de variables.

Merci pour votre aide, et le temps passé.

7 réponses

Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 656
17 août 2019 à 09:33
Bonjour

Les yeux experts sont peut-être en vacances...
Alors je me risque une remarque, sachant que
  • j'ai pas codé en VB6 depuis 12 ans
  • je bidouille en C++
  • je n'ai jamais codé de conversion d'un flottant à un autre (double et single sont déjà des flottants).


Mais je constate que le code VB6 ne fait pas la même chose que le code C++.
Alors je n'ai pas décortiqué, c'est certainement similaire, sinon tu n'aurais pas de résultats satisfaisants, mais ça n'est pas rigoureusement le même algorithme.

Dès le départ, le code C++ prend un double en paramètre et le VB6 un single, donc le C++ prend un flottant 32 bits et le VB un flottant 16 bits, rien que là il y a de la perte de précision.

Ensuite le code VB6, ne vérifie pas les bornes des short int, et de fait ne fait pas la conversion pour ces nombres.

Ici
If Num < 0 Then Num = -Num

Ça aurait pu être
Num = Abs(Num)
ça évite un if, c'est un peu plus rapide

Mes cours de maths datent un peu, mais la similarité entre ça
  while(num>=1.0)
    {
    num/=2.0; exp++;
    }

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

et
dbl2zx81.Exp = Int(Log(Num) / Log(2))

ne saute pas aux yeux

ici
  if(exp<-128 || exp>127) return(0);

If dbl2zx81.Exp < -129 Or dbl2zx81.Exp > 126 Then Exit Function

C'est pas les mêmes bornes.

Et pour la fin du code non plus, la similarité ne saute pas aux yeux
1
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 656
17 août 2019 à 18:13
J'ai essayé en VBA, c'est plus proche de VB6 que VB.net, mais
  • y'a pas de long non signé en VBA...
  • j'ai pas de valeurs tests pour vérifier, je t'en laisse le soin
  • je suis allé cherché les fonctions de décalage de bits, par flegme de les coder moi même


J'ai converti (enfin je pense), petit bloc par petit bloc, de façon bête et méchante.
Par exemple, si la division des logarithmes fait effectivement la même chose que les while, tant pis.


En commentaire le code C++ et le VBA ensuite
' ZX81 floating point number.
Type Zx81_Long
        exp As Integer ' - Exponent
        man As String ' - Mantissa
End Type

Function dbl2zx81(Num As Double) As Zx81_Long
    'int exp;
    'unsigned long man;
    Dim exp As Integer 'ça commence mal, il semble ne pas y avoir de unsigned long en VBA
    Dim man As Long '

    'if(num==(long)num && num>=-65535.0 && num<=65535.0)
    '{
    'long tmp=(long)fabs(num);
    
    'exp=0;
    'man=((tmp%256)<<16)|((tmp>>8)<<8);
    '}
    'else
    If Num = Int(Num) And Num >= -65535 And Num <= 65535 Then 'en vrai ce ne ce sont pas des And mais des AndAlso pour éviter de tester la condition suivante si c'est déjà Faux, mais apparemment il n'y en a pas en VBA
        Dim tmp As Long
        tmp = Abs(Num)
        
        exp = 0
        man = ShiftLeft(tmp Mod 256, 16) Or ShiftLeft(ShiftRight(tmp, 8), 8)
    Else
        'int f;
        'num=fabs(num);
  
        'exp=0;
        'while (Num >= 1.0)
        '{
        '  num/=2.0; exp++;
        '}

        'while (Num < 0.5)
        '{
            'num*=2.0; exp--;
        '}
        Dim f As Integer
        exp = 0
        
        While Num >= 1
            Num = Num / 2
            exp = exp + 1
        Wend
        
        While Num < 0.5
            Num = Num * 2
            exp = exp - 1
        Wend
        
        'if(exp<-128 || exp>127) return(0);
        'exp=128+exp;
        'num*=2.0;
        If exp < -128 Or exp > 127 Then Exit Function 'Ici cela auriat être OrElse, pour la même raison que AndAlso
        exp = 128 + exp
        Num = Num * 2
        
        'man=0;
        'for(f=0;f<32;f++)
        '{
            'man<<=1;
            'man|=(int)num;
            'num-=(int)num;
            'num*=2.0;
        '}
        man = 0
        For f = 1 To 32
            man = ShiftLeft(man, 1)
            Dim x As Integer
            x = Int(Num)
            man = man Or x
            Num = (Num - x) * 2
        Next
        
        'if((int)num && man!=0xFFFFFFFF) man++;
        If Not (Int(Num) = 0) And Not (man = &HFFFFFFFF) Then man = man + 1
        
        'man&=0x7FFFFFFF;
        '}
        man = man And &H7FFFFFFF
    End If
    
    '*pexp=exp; *pman=man;
    'return(1);
    dbl2zx81.exp = exp
    dbl2zx81.man = man
End Function

'Je suis allé cherché ShiftLeft et ShiftRight ici https://forum.hardware.fr/hfr/Programmation/VB-VBA-VBS/decallage-et-vb-sujet_33399_1.htm#t328274
Public Function ShiftLeft(ByVal m_Value As Long, ByVal m_Step As Long) As Long
Dim m_Counter As Long
  ShiftLeft = m_Value
 
  For m_Counter = 1 To m_Step
    ShiftLeft = ShiftLeft * 2
  Next
 
End Function

Public Function ShiftRight(ByVal m_Value As Long, ByVal m_Step As Long) As Long
Dim m_Counter As Long
  ShiftRight = m_Value
 
  For m_Counter = 1 To m_Step
    ShiftRight = ShiftRight \ 2
  Next
 
End Function

1
JeuDuTaquin Messages postés 249 Date d'inscription mardi 4 juillet 2017 Statut Membre Dernière intervention 31 mai 2023 7
Modifié le 18 août 2019 à 00:31
Salut Whismeril,

Réaction inattendue de l'émulateur… le code plante le décodeur en ROM, avec le "128" avec un -4,000C6E-52 ! Du jamais vu sur un Zx81 !

Le "C"... c'est une première.
Par contre, ".5" fonctionne très bien.
Bon, l'erreur est trop grosse pour être compliquée, surement une inversion en little ou big indiens dans le retour.


Je te donne un exemple du code du tocken correct…
0
JeuDuTaquin Messages postés 249 Date d'inscription mardi 4 juillet 2017 Statut Membre Dernière intervention 31 mai 2023 7
Modifié le 18 août 2019 à 00:33
Pour être plus explicite est illustrer le code, voici le codage des nombres:

Où "7E" est le tag de type de variable (ici numérique fp) exp[0] man[1-4], donc 5 octets.
Je regarde le code de plus près et je te donne des nouvelles…

Merci pour le code, qui me donnera sûrement la partie manquante.
0
JeuDuTaquin Messages postés 249 Date d'inscription mardi 4 juillet 2017 Statut Membre Dernière intervention 31 mai 2023 7
Modifié le 18 août 2019 à 00:50
J'ai un doute ici:
        'man=0;
        'for(f=0;f<32;f++)
        '{
            'man<<=1;
            'man|=(int)num; /* le caractère ' / ' a changé !!! */
            'num-=(int)num;
            'num*=2.0;
        '}
        man = 0
        For f = 1 To 32
            man = ShiftLeft(man, 1)
            Dim x As Integer
            x = Int(Num)
            man = man Or x
            Num = (Num - x) * 2
        Next

"|" au lieu de "/" … donc:
man = man / x
Mais ça ne change rien...
0
JeuDuTaquin Messages postés 249 Date d'inscription mardi 4 juillet 2017 Statut Membre Dernière intervention 31 mai 2023 7
18 août 2019 à 02:06
La procédure inverse floating vers nombre décodé..
Sub DecodeLngZX(Char_STR As String, Lng_Val As Double)
'Sub from ZxList 43 - David Gonzales, Renton , WA (Jan 15, 1995)
Dim NulCHR As String
Dim GetChar1 As Variant
Dim cmpt As Variant
Dim ExpEnd As Variant
Dim Enum_Mant As String
Dim lMant As Variant
Dim Expon As Byte
Dim ExpRes As Variant
Dim multi As Variant

NulCHR = Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0)

If Char_STR$ = NulCHR Then Lng_Val = 0: Exit Sub

GetChar1 = Asc(Mid(Char_STR$, 2, 1))
ExpEnd = GetChar1 And 128

Mid(Char_STR$, 2, 1) = Chr$(GetChar1 Or 128)
 
 For cmpt = 5 To 2 Step -1
    If Asc(Mid(Char_STR$, cmpt, 1)) Then Exit For
 Next
 
Enum_Mant$ = Mid(Char_STR$, 2, cmpt - 1)
lMant = Len(Enum_Mant$) * 8
Expon = Asc(Mid(Char_STR$, 1, 1))
ExpRes = Expon And 127

Lng_Val = 0
multi = 0
For cmpt = Len(Enum_Mant$) To 1 Step -1
    
    Lng_Val = Lng_Val + (Asc(Mid(Enum_Mant$, cmpt, 1)) * (256 ^ multi))
    multi = multi + 1
Next cmpt
    
Lng_Val = Lng_Val * (2 ^ (ExpRes - lMant))
Lng_Val = (Round(Lng_Val))

If ExpEnd Then Lng_Val = -Lng_Val

End Sub
0
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 656
18 août 2019 à 08:58
Bonjour
Cela me surprend qu’un caractère change pendant un copié-collé.
Mais soit.

Par contre, j’ai mis Int sans trop réfléchir, Int dans ma mémoire c’était la partie entière et point barre.
Mais non, j’ai vérifié ce matin, c’est le nombre entier en dessous, donc ça fait la même chose que Fix pour les nombres positifs, mais pas pour les négatifs.

C’est donc surprenant que cela change quelque chose à la ligne
tmpint2 = IntNum - (Int(IntNum / 65536#) * 65536#)
puisque IntNum est positif.
1
JeuDuTaquin Messages postés 249 Date d'inscription mardi 4 juillet 2017 Statut Membre Dernière intervention 31 mai 2023 7
18 août 2019 à 19:57
Oui,
tmpint2 = IntNum - (Fix(IntNum / 65536#) * 65536#) - 28

Et le "-28"... n'avait rien à faire ici !
Surement pour compenser une valeur de débogage...
Pour moi, les tests sont cohérents, donc le cahier des charge est rempli.
(j'ai pas testé toutes les valeurs… mais bon… les entiers sont ok!)
Encore merci.
0
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 656
18 août 2019 à 22:38
ha voilà ;)
0
JeuDuTaquin Messages postés 249 Date d'inscription mardi 4 juillet 2017 Statut Membre Dernière intervention 31 mai 2023 7
17 août 2019 à 17:15
Salut Whismeril,

Bien, c'est modifié…
Mais, après des heures de patinages, de bidouillages et de tests… ça marche, ça marche pas!
Je ne peux plus te dire pourquoi le bornage a changé…

Mais, maintenant, le "128" fonctionne bien.

Je ne soupçonne pas la chance du "débutant" pour ta part, car ton aide est précieuse et appréciée sur ce forum… donc, tu débutes à chaque message !

Pour le moment, j'ai testé qu'une valeur…
Il ce peut qu'il y en a d'autres.

Donc, bornage modifié, le ABS et le num a été déclaré en (Num As Double).

Pour tout te dire, il était en INTEGER au début, car la valeur d'entrée est une base VAL("123"), qui est un entier (en sortie en VB...je n'en suis pas sûr)… mais, il est soumis à des calculs… Donc, modifié pour évité des erreurs de débordement numérique.

C'est le problème, si on utilise une variable d'entrée, comme variable de calcul…
Donc, je teste d'autres valeurs… mais, au moins, ça fait une erreur en moins.

Merci Whismeril, je teste d'autres variables ".1", ".5"...
Et je te donnes des nouvelles.
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
JeuDuTaquin Messages postés 249 Date d'inscription mardi 4 juillet 2017 Statut Membre Dernière intervention 31 mai 2023 7
17 août 2019 à 17:32
J'ai une petite imprécision sur le ".1" qui donne "0.10000191"...
0
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 656
17 août 2019 à 18:18
Hum, malheureusement avec les flottants, l'imprécision est existante.
Reste à savoir si c'est normal ou pas.
Que donne le code C++ pour 0.1?
0
JeuDuTaquin Messages postés 249 Date d'inscription mardi 4 juillet 2017 Statut Membre Dernière intervention 31 mai 2023 7
Modifié le 18 août 2019 à 06:32
Tes derniers messages (avec tout plein de INT), mon mis la puce à l'oreille.
Donc, j'ai mis des int sur les valeurs à virgules…

Et avec cette ligne :
tmpint2 = IntNum - (Int(IntNum / 65536#) * 65536#)

Cela fonctionne !

J'ai "0,1" pour …".1"...
Donc, en cumulant les bonnes idées de ton côté, et mon esprit de … logique d'approximation pifométrique… ont a réussi.

Bon, voilà un boulet en moins, que je traîne depuis près de 5 ans.

Mais, comme un utilisateur s'en est aperçu… (pas de bol!)

Merci encore Whismeril pour ton approche de comparaison qui a permis de trouver l'erreur.

Le plus difficile dans ces bogues, c'est de se replonger dans le code, mais le fait d'avoir documenter ma question et d'avoir extrait les codes, m'ont permis de mieux appréhender les choses.

Et demander de l'aide, oblige à travailler dessus un minimum…

j'attends ta réponse pour clôturer le fil.
Vb81 XuR sera mis à jour dès que possible.
Merci à tous d'avoir suivi ce fil.
0
JeuDuTaquin Messages postés 249 Date d'inscription mardi 4 juillet 2017 Statut Membre Dernière intervention 31 mai 2023 7
18 août 2019 à 07:19
Juste une petite chose au sujet des options de compilation…
J'ai remarqué des problèmes numériques s'ajoutant aux erreurs de programmation.
Pour accélérer le programme compilé, j'ai tout coché!
Et bien non, il faut pas…

Voilà,voilà...
0
Rejoignez-nous