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); 
8
  • 2
    I'd strongly consider using decimal over double in these calculations. Commented Nov 11, 2011 at 20:12
  • 1
    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. Commented Nov 11, 2011 at 20:13
  • 2
    What's wrong with Math.Round() ? Commented Nov 11, 2011 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. Commented Nov 11, 2011 at 20:17
  • 1
    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. Commented Nov 11, 2011 at 20:24

3 Answers 3

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 ; } } 
Sign up to request clarification or add additional context in comments.

3 Comments

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.
@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.
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.
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.

9 Comments

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. "
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.
@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"
@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).
You could also use the modulo operator: int xF = (int)(d % 1m * 100000000m);
|
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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.