双精度,整型,数学.c# Round

本文关键字:Round 整型 双精度 数学 | 更新日期: 2023-09-27 18:14:21

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

"x字段由两个带符号的32位整数组成:x_i代表整数部分,x_f代表小数部分乘以10^8。例如:x(80.99)将使x_i为80,x_f为99,000,000"

首先,我尝试了以下操作,但有时似乎会失败,当xF值应该是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);

双精度,整型,数学.c# Round

问题是:(1)二进制浮点数以精度换取范围;(2)某些值,如3.1 不能用标准二进制浮点数格式(如IEEE 754-2008)精确地表示

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

请参阅以下页面,了解使用浮点数存储精确值的更多危险、缺陷和陷阱:

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

为什么要滚动你自己的when系统。小数能给出精确的小数浮点数吗?

但是,如果你要这样做,像这样做应该很好:

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

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

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

EDIT:与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%的纯双精度转换失败,其中当数字首先转换为十进制时,没有转换失败。

有两个问题:

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

  2. 转换到int总是向零舍入。这就是为什么,如果i.20000000小于i.2,您将得到小数部分的19999999。使用Convert.ToInt32轮接近,这是你在这里想要的。它会在所有情况下显示20000000

所以,假设你所有的数字都在0 - 99999999.99999999的范围内,下面的方法总是会给你最接近的解决方案:

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

当然,正如其他人所建议的,转换为decimal并使用它进行计算是一个很好的选择。