Pregunta

En el código a continuación, uso mpf_add para agregar la representación de cadena de dos valores flotantes. Lo que no entiendo en este momento es por qué 2.2 + 3.2 = 5.39999999999999999999999999999999999999 . Pensé que gmp era lo suficientemente inteligente como para dar 5.4 .

¿Qué es lo que no entiendo sobre cómo gmp hace flotar?

(Por cierto, cuando escribí esto por primera vez, no estaba seguro de cómo insertar un punto decimal, por lo tanto, la cifra de más / menos dígitos al final)

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;
}
¿Fue útil?

Solución

Esto se debe al error inherente de usar la aritmética de punto flotante en un entorno binario.

Consulte el estándar IEEE 754 para obtener más información.

Otros consejos

Lo que warren dijo .

Es posible que obtengas mejores resultados si utilizas decimales codificados en binario en lugar de números de punto flotante, aunque realmente no puedo dirigirte a ninguna biblioteca para eso.

Finalmente terminé respondiendo esto yo mismo. La solución para mí era hacer en código lo que solía hacer en la escuela. El método funciona así:

  1. Tome cada número y asegúrese de que el número de dígitos a la derecha del punto decimal sea el mismo. Por lo tanto, si agrega 2.1 y 3.457 , 'normalice' el primero a 2.100 . Mantenga un registro de la cantidad de dígitos que están a la derecha del decimal, en este caso, tres.
  2. Ahora quite el punto decimal y use mpz_add para agregar los dos números, que ahora se han convertido en 2100 y 3457 . El resultado es 5557 .
  3. Finalmente, vuelva a insertar el punto decimal tres caracteres (en este caso) de la derecha, dando la respuesta correcta de 5.557 .

Prototipé la solución en VBScript (a continuación)

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

y finalmente en Visual C ++ (abajo).

#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;
}

Acepto que la C es un poco ... bueno ... dudosa, así que no dude en criticarla. Todos los comentarios útiles recibidos con gratitud.

Pasé de aquí para implementar FSUB y FMUL también. FDIV no fue tan satisfactorio, terminó en tres versiones y usó números racionales.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top