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