Ajouter des flotteurs avec gmp donne des résultats" corrects", en quelque sorte

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

Qu'est-ce que je ne comprends pas sur la façon dont gmp flotte?

(BTW, quand j'ai écrit pour la première fois ceci je n'étais pas sûr Comment insérer un point décimal, donc le plus/moins chiffre trucs à 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;
}
1
demandé sur bugmagnet 2008-10-07 19:14:39

3 réponses

c'est en raison de l'erreur inhérente de l'utilisation de l'arithmétique flottante dans un environnement binaire.

voir la norme IEEE 754 pour plus d'information.

4
répondu warren 2008-10-07 15:18:00

Ce warren dit .

vous pourriez avoir de meilleurs résultats si vous utilisez des nombres décimaux codés binaires au lieu de nombres à virgule flottante, bien que je ne puisse pas vraiment vous diriger vers des bibliothèques pour cela.

1
répondu Chris Charabaruk 2017-05-23 11:52:08

j'ai fini par 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 numéro et assurez-vous que le nombre de chiffres à la droite du point décimal sont les mêmes. Donc, si vous ajoutez 2.1 et 3.457 , "normalisez" le premier à 2.100 . Gardez une trace du nombre de chiffres à droite du séparateur décimal, dans ce cas, trois.
  2. maintenant supprimer le point décimal et utiliser mpz_add pour ajouter les deux nombres, qui sont devenus 2100 et 3457 . Le résultat est 5557 .
  3. enfin, réinsérer le point décimal trois caractères (dans ce cas) de la droite, donnant la réponse correcte de 5.557 .

je prototypage de 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 = '"151910920"';
  } else {
    *dotPos = '"151910920"';
    strcpy( LeftOf1, sP1 );
    strcpy( RightOf1, (dotPos + 1) );
  }

  dotPos = strstr( sP2, "." );
  if ( dotPos == NULL ) {
    strcpy( LeftOf2, sP2 );
    *RightOf2 = '"151910920"';
  } else {
    *dotPos = '"151910920"';
    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 que le C est un peu ... bien. .. dodgy, alors n'hésitez pas à le critiquer. Tous les commentaires utiles ont été reçus avec gratitude.

je suis allé d'ici à mettre en œuvre FSUB et FMUL ainsi. FDIV n'était pas aussi satisfaisant, se retrouvant en trois versions et utilisant des nombres rationnels.

1
répondu bugmagnet 2008-10-08 18:28:44