Question

Dans le code ci-dessous, j'utilise mpf_add pour ajouter la représentation sous forme de chaîne de deux valeurs flottantes. Ce que je ne comprends pas à ce stade, c’est pourquoi 2.2 + 3.2 = 5.399999999999999999999999999999999999 . J'aurais pensé que gmp était assez intelligent pour donner 5.4 .

Qu'est-ce que je ne comprends pas sur la manière dont gmp flotte?

(Au fait, quand j'ai écrit ceci pour la première fois, je ne savais pas vraiment comment insérer un point décimal, donc le nombre de chiffres plus / moins à la fin)

BSTR __stdcall FBIGSUM(BSTR p1, BSTR p2 ) {
  USES_CONVERSION;

  F(n1);
  F(n2);
  F(res);

  LPSTR sNum1 = W2A( p1 );
  LPSTR sNum2 = W2A( p2 );

  mpf_set_str( n1, sNum1, 10 );
  mpf_set_str( n2, sNum2, 10 );

  mpf_add( res, n1, n2 );

  char * buff =  (char *) _alloca( 1024 );
  char expBuffer[ 20 ];
  mp_exp_t exp;

  mpf_get_str(buff, &exp, 10, 0, res);

  char * temp = ltoa( (long) exp, expBuffer, 10 );
  if (exp >= 0) {
    strcat(buff, "+" );
  }
  strcat(buff, expBuffer );

  BSTR bResult = _com_util::ConvertStringToBSTR( buff );
  return bResult;
}
Était-ce utile?

La solution

Cela est dû à l'erreur inhérente à l'utilisation de l'arithmétique à virgule flottante dans un environnement binaire.

Consultez la norme IEEE 754 pour plus d'informations.

Autres conseils

Qu'est-ce que warren a déclaré .

Vous obtiendrez peut-être de meilleurs résultats si vous utilisez une décimale codée binaire au lieu de nombres à virgule flottante, bien que je ne puisse pas vraiment vous diriger vers une bibliothèque pour cela.

J'ai finalement fini par y répondre moi-même. La solution pour moi était de faire en code ce que je faisais à l’école. La méthode fonctionne comme ceci:

  1. Prenez chaque nombre et assurez-vous que le nombre de chiffres à droite du point décimal est le même. Donc, si vous ajoutez 2.1 et 3.457 , normalisez le premier en 2.100 . Conservez le nombre de chiffres à droite de la décimale, dans ce cas trois.
  2. Supprimez maintenant le point décimal et utilisez mpz_add pour ajouter les deux nombres, qui sont désormais devenus 2100 et 3457 . Le résultat est 5557 .
  3. Enfin, réinsérez le signe décimal trois caractères (dans ce cas) en partant de la droite, en donnant la réponse correcte de 5.557 .

J'ai prototypé la solution en VBScript (ci-dessous)

function fadd( n1, n2 )
    dim s1, s2, max, mul, res
    normalise3 n1, n2, s1, s2, max
    s1 = replace( s1, ".", "" )
    s2 = replace( s2, ".", "" )
    mul = clng(s1) + clng(s2)
    res = left( mul, len(mul) - max ) & "." & mid( mul, len( mul ) - max + 1 )
    fadd = res
end function

sub normalise3( byval n1, byval n2, byref s1, byref s2, byref numOfDigits )
    dim a1, a2
    dim max
    if instr( n1, "." ) = 0 then n1 = n1 & "."
    if instr( n2, "." ) = 0 then n2 = n2 & "."
    a1 = split( n1, "." )
    a2 = split( n2, "." )
    max = len( a1(1) )
    if len( a2(1) ) > max then max = len( a2( 1 ) )
    s1 = a1(0) & "." & a1(1) & string( max - len( a1( 1 )), "0" )
    s2 = a2(0) & "." & a2(1) & string( max - len( a2( 1 )), "0" )
    numOfDigits = max
end sub

et enfin dans Visual C ++ (ci-dessous).

#define Z(x) mpz_t x; mpz_init( x );

BSTR __stdcall FADD( BSTR p1, BSTR p2 ) {
  USES_CONVERSION;

  LPSTR sP1 = W2A( p1 );
  LPSTR sP2 = W2A( p2 );

  char LeftOf1[ 1024 ];
  char RightOf1[ 1024 ];
  char LeftOf2[ 1024 ];
  char RightOf2[ 1024 ];
  char * dotPos;
  long numOfDigits;
  int i;
  int amtOfZeroes;

  dotPos = strstr( sP1, "." );
  if ( dotPos == NULL ) {
    strcpy( LeftOf1, sP1 );
    *RightOf1 = '\0';
  } else {
    *dotPos = '\0';
    strcpy( LeftOf1, sP1 );
    strcpy( RightOf1, (dotPos + 1) );
  }

  dotPos = strstr( sP2, "." );
  if ( dotPos == NULL ) {
    strcpy( LeftOf2, sP2 );
    *RightOf2 = '\0';
  } else {
    *dotPos = '\0';
    strcpy( LeftOf2, sP2 );
    strcpy( RightOf2, (dotPos + 1) );
  }

  numOfDigits = strlen( RightOf1 ) > strlen( RightOf2 ) ? strlen( RightOf1 ) : strlen( RightOf2 );

  strcpy( sP1, LeftOf1 );
  strcat( sP1, RightOf1 );
  amtOfZeroes = numOfDigits - strlen( RightOf1 );
  for ( i = 0; i < amtOfZeroes; i++ ) {
    strcat( sP1, "0" );
  }
  strcpy( sP2, LeftOf2 );
  strcat( sP2, RightOf2 );
  amtOfZeroes = numOfDigits - strlen( RightOf2 );
  for ( i = 0; i < amtOfZeroes; i++ ) {
    strcat( sP2, "0" );
  }


  Z(n1);
  Z(n2);
  Z(res);

  mpz_set_str( n1, sP1, 10 );
  mpz_set_str( n2, sP2, 10 );
  mpz_add( res, n1, n2 );

  char * buff =  (char *) _alloca( mpz_sizeinbase( res, 10 ) + 2 + 1 );

  mpz_get_str(buff, 10, res);

  char * here = buff + strlen(buff) - numOfDigits; 

  memmove( here + 1, here, strlen(buff)); // plus trailing null
  *(here) = '.';

  BSTR bResult = _com_util::ConvertStringToBSTR( buff );
  return bResult;
}

J'accepte le fait que le C est un peu ... bien ... douteux, alors n'hésitez pas à le critiquer. Tous les commentaires utiles reçus avec gratitude.

À partir de là, j'ai également implémenté FSUB et FMUL. FDIV n’était pas aussi satisfaisant, se terminant en trois versions et utilisant des nombres rationnels.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top