C#中的双打,Ints,Math.Round

时间:2011-11-11 20:09:33

标签: c# .net double rounding

我必须将双值x转换为两个整数,如下所示......

“x字段由两个带符号的32位整数组成:x_i表示整数部分,x_f表示小数部分乘以10 ^ 8.例如:x为80.99,x_i为80,x_f为99,000,000”

首先我尝试了以下内容,但有时似乎失败了,当它应该是2000000时,xF值为1999999

// Doesn't work, sometimes we get 1999999 in the xF
int xI = (int)x;
int xF = (int)(((x - (double)xI) * 100000000));

以下似乎适用于我测试过的所有情况。但我想知道如果没有圆形通话,是否有更好的方法可以做到这一点。而且,是否有可能仍然失败的情况?

// Works, we get 2000000 but there's the round call
int xI = (int)x;
double temp = Math.Round(x - (double)xI, 6);
int xF = (int)(temp * 100000000);

3 个答案:

答案 0 :(得分:2)

问题是(1)二进制浮点交换范围的精度和(2)某些值,例如3.1 不能完全以标准二进制浮点格式(例如IEEE 754-)表示2008。

首先阅读David Goldberg的"What Every Computer Scientist Should Know About Floating-Point Arithmetic",发表于 ACM Computing Surveys ,第23卷,第1期,1991年3月。

然后查看这些页面,了解有关使用浮点数存储确切值的危险,陷阱和陷阱的更多信息:

http://steve.hollasch.net/cgindex/coding/ieeefloat.html http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm

为什么在System.Decimal为您提供精确的十进制浮点时自己滚动?

但是,如果你要这样做,这样的事情就可以了:

struct WonkyNumber
{
    private const double SCALE_FACTOR    = 1.0E+8          ;
    private int          _intValue        ;
    private int          _fractionalValue ;
    private double       _doubleValue     ;

    public int    IntegralValue
    {
        get
        {
            return _intValue ;
        }
        set
        {
            _intValue = value ;
            _doubleValue = ComputeDouble() ;
        }
    }
    public int    FractionalValue
    {
        get
        {
            return _fractionalValue ;
        }
        set
        {
            _fractionalValue = value ;
            _doubleValue     = ComputeDouble() ;
        }
    }
    public double DoubleValue
    {
        get
        {
            return _doubleValue ;
        }
        set
        {
            this.DoubleValue = value ;
            ParseDouble( out _intValue , out _fractionalValue ) ;
        }
    }

    public WonkyNumber( double value ) : this()
    {
        _doubleValue = value ;
        ParseDouble( out _intValue , out _fractionalValue ) ;
    }

    public WonkyNumber( int x , int y ) : this()
    {

        _intValue        = x ;
        _fractionalValue = y ;
        _doubleValue     = ComputeDouble() ;

        return ;
    }

    private void ParseDouble( out int x , out int y )
    {
        double remainder = _doubleValue % 1.0 ;
        double quotient  = _doubleValue - remainder ;

        x = (int)   quotient                   ;
        y = (int) Math.Round( remainder * SCALE_FACTOR ) ;

        return ;
    }

    private double ComputeDouble()
    {
        double value =     (double) this.IntegralValue
                     + ( ( (double) this.FractionalValue ) / SCALE_FACTOR )
                     ;
        return value ;
    }

    public static implicit operator WonkyNumber( double value )
    {
        WonkyNumber instance = new WonkyNumber( value ) ;
        return instance ;
    }

    public static implicit operator double( WonkyNumber value )
    {
        double instance = value.DoubleValue ;
        return instance ;
    }

}

答案 1 :(得分:0)

我认为使用小数来解决问题,因为在内部他们确实使用了数字的十进制表示。使用double时,将数字的二进制表示转换为十进制时会出现舍入错误。试试这个:

double x = 1234567.2;
decimal d = (decimal)x;
int xI = (int)d;
int xF = (int)(((d - xI) * 100000000)); 

编辑:与RuneFS的无休止讨论表明事情并非那么容易。因此,我做了一个非常简单的测试,有一百万次迭代:

public static void TestDecimals()
{
    int doubleFailures = 0;
    int decimalFailures = 0;
    for (int i = 0; i < 1000000; i++) {
            double x = 1234567.7 + (13*i);
            int frac = FracUsingDouble(x);
            if (frac != 70000000) {
                    doubleFailures++;
            }
            frac = FracUsingDecimal(x);
            if (frac != 70000000) {
                    decimalFailures++;
            }
    }
    Console.WriteLine("Failures with double:  {0}", doubleFailures);  // => 516042
    Console.WriteLine("Failures with decimal: {0}", decimalFailures); // => 0
    Console.ReadKey();
}

private static int FracUsingDouble(double x)
{
    int xI = (int)x;
    int xF = (int)(((x - xI) * 100000000));
    return xF;
}

private static int FracUsingDecimal(double x)
{
    decimal d = (decimal)x;
    int xI = (int)d;
    int xF = (int)(((d - xI) * 100000000));
    return xF;
}

在此测试中,51.6%的双精度转换失败,当数字首先转换为十进制时,没有转换失败。

答案 2 :(得分:0)

有两个问题:

  1. 您的输入值很少等于小数点后8位数的十进制表示。所以某种舍入是不可避免的。换句话说:您的号码i.20000000实际上会比i.2稍微或稍微多一点。

  2. 施放到int始终向零舍入。这就是为什么,如果i.20000000小于i.2,您将获得小数部分的19999999。使用Convert.ToInt32轮到最近,这是你想要的。在所有情况下,它都会为您提供20000000

  3. 因此,如果您的所有数字都在0 - 99999999.99999999范围内,以下内容将始终为您提供最近的解决方案:

    int xI = (int)x; 
    int xF = Convert.ToInt32((x - (double)xI) * 100000000); 
    

    当然,正如其他人所建议的那样,转换为decimal并将其用于计算是一个很好的选择。