2

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);
Michael Covelli
  • 2,113
  • 4
  • 27
  • 41
  • 2
    I'd strongly consider using decimal over double in these calculations. – CodesInChaos Nov 11 '11 at 20:12
  • Thanks. Unfortunately I am passed a double here and have to return 2 integers with no ability to change that. I could convert from a double to a decimal before doing the calculations if that will help. – Michael Covelli Nov 11 '11 at 20:13
  • 2
    What's wrong with `Math.Round()` ? – H H Nov 11 '11 at 20:16
  • Not sure is it will help, too tired atm to think it through. But the concept of decimal digits when working with doubles is a bit strange. – CodesInChaos Nov 11 '11 at 20:17
  • Nothing is wrong with Math.Round() other than I don't know how its implemented. I'm mostly curious if there could be cases where my second solution could still fail. I so rarely have to look at the nuances between base 2 and base 10 representations that I'm pretty rusty. I thought there might be some magic .NET function or library that did exactly what I was looking for here without me trying to write it from scratch. – Michael Covelli Nov 11 '11 at 20:20
  • Unfortunately, using doubles you're going to have to bandage it to get it running. Doubles are quite precise, but they're still not exact. Math.Round should always round the number, so what you have is likely as good as it gets. – drharris Nov 11 '11 at 20:24
  • If the requirements are _exactly_ as you've stated them. Then the implementation with Round is incorrect. you do not simply multiply by 10^8, you do something entirely different – Rune FS Nov 11 '11 at 20:38
  • The requirements are exactly as I've stated them. This is being sent in a packet over a socket to a process running on a SLES10 machine. I have no ability to change their code unfortunately. – Michael Covelli Nov 11 '11 at 20:44

3 Answers3

2

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

}
Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
  • Well I don't want to create my own class like this if I don't have to. I would love to use decimal except the value that I'm receiving is a double. One option is to just convert the double to a decimal as Olivier suggests below. Not sure if that will work. But something like that is what I'm looking for. – Michael Covelli Nov 11 '11 at 21:08
  • @MichaelCovelli: see my amended answer. If you're receiving a double value, you'll need to deal with the jitter and lack of precision and round the value to the precision you need. – Nicholas Carey Nov 11 '11 at 21:22
  • An option which would work for certain, would be to convert the double to string and then split the string at the decimal point and then convert the 2 parts back to int. – Olivier Jacot-Descombes Nov 13 '11 at 02:36
0

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.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • 1
    And how do you suggest to convert a double to a decimal with out the problem: "With double you get rounding errors when converting the binary representation of a number to decimal. " – Rune FS Nov 11 '11 at 20:40
  • Maybe the double to decimal conversion is smart enough to avoid the most common of errors when doing this type of conversion. Not sure yet. – Michael Covelli Nov 11 '11 at 20:43
  • @MichaelCovelli my point is that it's an oxymoron to say that you can't convert precisely from double to decimal, "It cannot be done unless you do it" – Rune FS Nov 11 '11 at 21:02
  • @RuneFS: A rounding error might indeed occur when converting from double to decimal, but this is not a problem here. The crucial point is the calculation of (d - xI) * 100000000. Especially (d - xI). – Olivier Jacot-Descombes Nov 12 '11 at 18:01
  • You could also use the modulo operator: int xF = (int)(d % 1m * 100000000m); – Olivier Jacot-Descombes Nov 12 '11 at 18:06
  • Since there are numbers that can be represented precisely with double but _not_ with decimal. Your solution is simply moving the problem from the calculation (4th line) to the conversion (2nd line). – Rune FS Nov 13 '11 at 19:27
  • @RuneFS: May be the problem already occurs when the decimal literal is converted to a double by the compiler. The double might alreay contain 1234567.199999 before any calculation is done. Since the number will have to be presented to the user in a decimal notation, it will have to be converted anyway, so better do it before the calculation, in order to preserve as much precision as possible. – Olivier Jacot-Descombes Nov 13 '11 at 20:07
  • @MichaelCovelli: Since you multiply the fraction with 10^8, you could add 10^-9 to the original number before the conversion, in order to get rid of the 9999999999. – Olivier Jacot-Descombes Nov 13 '11 at 20:12
  • You are missing the point. The cast might _introduce_ the error. You can have scenarios where OPs code works just fine but yours fail – Rune FS Nov 14 '11 at 06:56
0

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.

Jeffrey Sax
  • 10,253
  • 3
  • 29
  • 40