Добавление поплавков с помощью gmp дает "правильные” результаты, своего рода

StackOverflow https://stackoverflow.com/questions/178952

Вопрос

В приведенном ниже коде я использую mpf_add чтобы добавить строковое представление двух плавающих значений.Чего я не понимаю на данный момент, так это почему 2.2 + 3.2 = 5.39999999999999999999999999999999999999.Я бы подумал , что gmp был достаточно умен, чтобы дать 5.4.

Чего я не понимаю в том, как gmp использует floats?

(Кстати, когда я впервые написал это, я не был уверен, как вставить десятичную точку, таким образом, плюс / минус цифры в конце)

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;
}
Это было полезно?

Решение

Это происходит из-за врожденной ошибки использования арифметики с плавающей запятой в двоичной среде.

Посмотрите на IEEE 754 стандарт для получения дополнительной информации.

Другие советы

Что warren сказал .

Вы могли бы получить лучшие результаты, если бы вы использовали двоичное десятичное число вместо чисел с плавающей запятой, хотя я не могу направить вас в какие-либо библиотеки для этого.

В конце концов я сам ответил на это. Решением для меня было сделать в коде то, что я делал в школе. Метод работает так:

<Ол>
  • Возьмите каждое число и убедитесь, что количество цифр справа от десятичной точки одинаково. Поэтому, если вы добавляете 2.1 и 3.457 , «нормализуйте» первое значение до 2.100 . Запишите количество цифр, которые находятся справа от десятичной дроби, в данном случае три.
  • Теперь удалите десятичную точку и используйте mpz_add , чтобы добавить два числа, которые теперь стали 2100 и 3457 . Результат - 5557 .
  • Наконец, заново вставьте десятичную точку на три символа (в данном случае) справа, давая правильный ответ 5.557 .
  • Я прототипировал решение в VBScript (ниже)

    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
    

    и, наконец, в Visual C ++ (ниже).

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

    Я принимаю, что C немного ... хорошо ... хитроумно, поэтому, пожалуйста, не стесняйтесь критиковать его. Все полезные комментарии с благодарностью получены.

    Я также начал внедрять FSUB и FMUL. FDIV был не таким уж и удовлетворительным: он закончился в трех версиях и использовал рациональные числа.

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top