【问题标题】:Adding floats with gmp gives "correct" results, sort of用 gmp 添加浮点数会给出“正确”的结果,有点像
【发布时间】:2008-10-07 15:14:39
【问题描述】:

在下面的代码中,我使用mpf_add 添加两个浮点值的字符串表示。我现在不明白为什么2.2 + 3.2 = 5.39999999999999999999999999999999999999。我会认为gmp 足够聪明,可以给5.4

关于 gmp 如何浮动,我有什么不明白的地方?

(顺便说一句,当我第一次写这篇文章时,我不确定如何插入小数点,因此末尾的加/减数字内容)

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

【问题讨论】:

  • 最简单的就是用mpq_t,有理数的运算是精确的。

标签: visual-c++ floating-point bignum gmp


【解决方案1】:

这是因为在二进制环境中使用浮点运算的固有错误。

有关详细信息,请参阅IEEE 754 标准。

【讨论】:

    【解决方案2】:

    什么warrensaid

    如果您使用二进制编码的十进制数而不是浮点数,您可能会得到更好的结果,尽管我不能真正将您引导到任何库。

    【讨论】:

      【解决方案3】:

      我最终自己回答了这个问题。对我来说,解决方案是用我以前在学校做的代码来做。该方法的工作原理如下:

      1. 取每个数字并确保小数点右侧的位数相同。因此,如果添加2.13.457,则将第一个“标准化”为2.100。记录小数点右边的位数,在本例中为三位。
      2. 现在去掉小数点并使用mpz_add 将两个数字相加,现在变成21003457。结果是5557
      3. 最后,重新插入小数点从右边三个字符(在这种情况下),给出正确答案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 有点……嗯……狡猾,所以请随时批评它。感谢所有有用的 cmets。

      我也从这里开始实施 FSUB 和 FMUL。 FDIV 几乎没有那么令人满意,最终有三个版本并使用有理数。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-01-31
        • 2019-07-08
        • 2016-05-02
        • 2020-11-27
        • 1970-01-01
        • 1970-01-01
        • 2016-04-30
        相关资源
        最近更新 更多