Reputation: 575
I have the following function:
public virtual long AsLong(object originalValue,long defaultValue)
{
double buffer = defaultValue;
if (originalValue != null)
{
double result;
var readValueIsconverted = double.TryParse(originalValue.ToString(), out result);
if (readValueIsconverted)
buffer = result;
}
var roundedValue = Math.Round(buffer, 0);
var convertedValue = (long) roundedValue;
return convertedValue;
}
I have used double in order to allow conversion of 14.4! I have the following failing test:
[Fact]
public void CanConvertLongMaxValue()
{
var cellValue = new Converter();
const long longValue = 0x7FFFFFFFFFFFFFFF;
var result = cellValue.AsLong(longValue, 12);
Assert.Equal(longValue, result);
}
I have traced the code and roundedValue is positive but the convertedValue is negative. so what is the problem?
Upvotes: 3
Views: 26359
Reputation: 61952
Check the type of your originalValue
. Its compile-time type is object
, but what is the real type at run-time? If it's a boxed number type, there's no need to call .ToString()
followed by TryParse
. It is better to unbox directly to the correct type (especially if it is always the same run-time type) and then convert to another numerical type if necessary.
If you send a long
with many digits, like 0x7FFFFFFFFFFFFFFF
which is the same as long.MaxValue
, through a double
, note that a long
has approximately 10 bits' higher precision than a double
. Technically, that's because a double
uses 11 bits for the exponent but doesn't have to store the most significant bit.
At the value of long.MaxValue
, the precision of a double
changes because the exponent goes up by one when we pass a power of two. double
values just below long.MaxValue
have a precision of 1024. This means that only whole multiples of 1024 can be represented. Not surprisingly, double
values just above long.MaxValue
have a precision of 2048. And of course the precision of the integer type long
is always exactly 1.
Here are the three nearest double
under and the three over long.MaxValue
:
9.22337203685477'27'36 E+18
9.22337203685477'37'60 E+18
9.22337203685477'47'84 E+18
---
9.22337203685477'58'08 E+18 <-- two to the 63rd power
9.22337203685477'78'56 E+18
9.22337203685477'99'04 E+18
When you convert long.MaxValue
, that is 9223372036854775807
to double
, the nearest representable is 9.223372036854775808E+18
which is 1.0
too large. Its string representation with .ToString()
will show only 15 digits, while .ToString("R")
will show 17 digits (the apostrophes '
in my list above). When converting the double
`9.223372036854775808E+18
back to long
, we get an overflow exception because the number is 1.0
too big. You can still convert the number to ulong
, of course, which should reveal the exact value.
Upvotes: 1
Reputation: 48486
The problem is that you're trying to hold an integer value with 19 significant digits (in decimal) in a double
which has 15-16 significant digits.
Hence, it's not possible to represent the value exactly in the double
. Apparently rounding causes the value to overflow when converted into a long, making it a negative value.
You can confirm this like so:
var convertedValue = checked((long)roundedValue);
If you absolutely must deal with this case, I would suggest using decimal
instead of double, or splitting the string on the decimal point (or whatever is used in your locale) and dealing with rounding that way.
Upvotes: 4
Reputation: 125630
You should check, if provided parameter is long
at the first place:
public virtual long AsLong(object originalValue,long defaultValue)
{
if(originalValue.GetType() == typeof(long))
return (long) originalValue;
double buffer = defaultValue;
...
}
Otherwise you can loose some information on long
to double
conversion.
Upvotes: 0