Doubles, Ints, Math.Round в C # - PullRequest
       51

Doubles, Ints, Math.Round в C #

2 голосов
/ 12 ноября 2011

Мне нужно преобразовать двойное значение x в два целых числа, как указано в следующем ...

"Поле x состоит из двух 32-разрядных целых чисел со знаком: x_i, представляющий неотъемлемую часть, и x_f, представляющийдробная часть, умноженная на 10 ^ 8. Например: x из 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);

Ответы [ 3 ]

2 голосов
/ 12 ноября 2011

Проблема состоит в том, что (1) двоичная точность операций с плавающей запятой для диапазона и (2) определенные значения, такие как 3.1 , не могут быть точно представлены в стандартных двоичных форматах с плавающей запятой, таких как IEEE 754-2008, например, .

Сначала прочитайте Дэвида Голдберга "Что должен знать каждый компьютерщик об арифметике с плавающей точкой" , опубликованной в ACM Computing Surveys , том 23, № 1, март 1991 года.

Затем посмотрите эти страницы, чтобы узнать больше об опасностях, ловушках и ловушках использования поплавков для хранения точных значений:

http://steve.hollasch.net/cgindex/coding/ieeefloat.html http://www.cygnus -software.com / документы / 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 ;
    }

}
0 голосов
/ 14 ноября 2011

Существует две проблемы:

  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 и использование его для ваших расчетов - отличный вариант.

0 голосов
/ 12 ноября 2011

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

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% преобразования только для двойных чисел, поскольку преобразование не удается, если число сначала преобразуется в десятичное число.

...