C# 中的双精度数、整数、Math.Round

发布于 2024-12-15 11:13:50 字数 586 浏览 2 评论 0原文

我必须将双精度值 x 转换为以下指定的两个整数...

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

首先我尝试了以下方法,但有时似乎会失败,给出 xF 值1999999 当它应该是 2000000

// 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);

I have to convert a double value x into two integers as specified by the following...

"x field consists of two signed 32 bit integers: x_i which represents the integral part and x_f which represents the fractional part multiplied by 10^8. e.g.: x of 80.99 will have x_i as 80 and x_f as 99,000,000"

First I tried the following, but it seems to fail sometimes, giving an xF value of 1999999 when it ought to be 2000000

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

The following seems to work in all the cases that I've tested. But I was wondering if there's a better way to do it without the round call. And also, could there be cases where this could still fail?

// 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);

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

我要还你自由 2024-12-22 11:13:50

问题是 (1) 二进制浮点以精度换取范围,以及 (2) 某些值(例如 3.1)无法以标准二进制浮点格式(例如 IEEE 754-2008)精确表示。

首先阅读 David Goldberg 的 “What Every”计算机科学家应该了解浮点算术”,发表于 ACM 计算调查,第 23 卷,第 1 期,3 月1991。

然后请参阅这些页面,了解有关使用浮点数存储精确值的危险、陷阱和陷阱的更多信息:

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

}

The problem is (1) that binary floating point trades precision for range and (2) certain values, such as 3.1 cannot be repsented exactly in standard binary floating point formats, such as IEEE 754-2008.

First read David Goldberg's "What Every Computer Scientist Should Know About Floating-Point Arithmetic", published in ACM Computing Surveys, Vol 23, No 1, March 1991.

Then see these pages for more on the dangers, pitfalls and traps of using floats to store exact values:

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

Why roll your own when System.Decimal gives you precise decimal floating point?

But, if your going to do it, something like this should do you just fine:

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

}
假装爱人 2024-12-22 11:13:50

我认为使用小数可以解决问题,因为在内部它们实际上使用数字的十进制表示形式。使用 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% 的仅双精度转换失败,而当数字首先转换为十进制时,没有转换失败。

I think using decimals solve the problem, because internally they really use a decimal representation of the numbers. With double you get rounding errors when converting the binary representation of a number to decimal. Try this:

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

EDIT: The endless discussion with RuneFS shows that the matter is not that easy. Therefore I made a very simple test with one million iterations:

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

In this Test 51.6% of the doubles-only conversion fail, where as no conversion fails when the number is converted to decimal first.

江心雾 2024-12-22 11:13:50

有两个问题:

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

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

There are two issues:

  1. Your input value will rarely be equal to its decimal representation with 8 digits after the decimal point. So some kind of rounding is inevitable. In other words: your number i.20000000 will actually be slightly less or slightly more than i.2.

  2. Casting to int always rounds towards zero. This is why, if i.20000000 is less than i.2, you will get 19999999 for the fractional part. Using Convert.ToInt32 rounds to nearest, which is what you'll want here. It will give you 20000000 in all cases.

So, provided all your numbers are in the range 0-99999999.99999999, the following will always get you the nearest solution:

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

Of course, as others have suggested, converting to decimal and using that for your calculations is an excellent option.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文