L’ajout de flotteurs avec gmp donne & # 8220; correct & # 8221; résultats, sorte de
-
05-07-2019 - |
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;
}
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
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:
- 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
et3.457
, normalisez le premier en2.100
. Conservez le nombre de chiffres à droite de la décimale, dans ce cas trois. - Supprimez maintenant le point décimal et utilisez
mpz_add
pour ajouter les deux nombres, qui sont désormais devenus2100
et3457
. Le résultat est5557
. - 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.