tags:

views:

119

answers:

5

I have a problem with conversion from double to decimal:

 public class CartesianCoordinates
    {
        public int LatitudeHours { get; set;}
        public int LatitudeMinutes { get; set; }
        public int LatitudeSeconds { get; set; }
        public GeoDirectionLongtitude LongitudeDirection { get; set; }

        public int LongitudeHours { get; set; }
        public int LongitudeMinutes { get; set; }
        public int LongitudeSeconds { get; set; }
        public GeoDirectionLatitude LatitudeDirection { get; set; }
    }

public class DecimalCoordinates
    {
        public decimal Latitude { get; set; }
        public decimal Longitude { get; set; }
    }

CartesianCoordinates CartesianCoordinates=new CartesianCoordinates(){LatitudeHours =12,LatitudeMinutes =34,LatitudeSeconds=56 }

     converterDecimalCoordinates.Latitude = CartesianCoordinates.LatitudeHours + (CartesianCoordinates.LatitudeMinutes + (CartesianCoordinates.LatitudeSeconds / 60)) / 60;

Why I get 12 ? I want 12,55

+8  A: 

All your calculations are integer ones and are being rounded (ore more accurately, truncated). Try replacing your literal values of 60 with 60m to force a decimal calculation, or 60.0 to force a double calculation (in which case you'll need to convert to a decimal at the end).

David M
Just a note, the values are not rounded in the expected sense - more truncated. 1.99 will cast to 1 for an int.
Adam
+3  A: 

You get 12 because the calculation is operating on int types for certain parts of it and thus isn't able to contain the precision - the value gets truncated to an int. I would convert all your values into decimals prior to calculating - or as other answers have suggested, specify your literals as decimal numbers.

Update: as highlighted in other posts, this is currently performing an integer division - I wasn't aware of the technical term, so like you, I've learnt something today lol

Adam
It requires only one decimal in the calculation to do the calculation as decimal. Converting all of them is a bit of a hassle.
Abel
To be honest, I didn't pay too much attention to the specifics in the calculation itself - but I did understand the loss in precision was being introduced due to integer division - other answers since clarified this point. Also, calculations like this often improve in readability at the cost of a bit of hassle, a nice trade-off IMO. I get no love :-)
Adam
I was wrong, partially, and you do get the love (and credits +1) you deserve, I expanded the comment-threads in an answer ;-)
Abel
+7  A: 
Int32 x = 10;

Decimal y = x / 4;  // Performs an integer devision - result is 2.0
Decimal z = x / 4M; // Performs a decimal devision - result is 2.25

You get an integer devision if both operands are integers. By adding the suffix M to a number you can explicitly state that the number should be interpreted as a decimal number and therefore you will get a decimal devision.

Daniel Brückner
I regularly come across `Decimal x = 1M * [.. your calculation ..]` which is a quick and easy way to improve readability and robustness against inadvertent editing by placing the conversion-literal up front.
Abel
@Abel - doesn't help, the calculation will still be performed as an integer division in this case. Post-conversion to a decimal by multiplication won't affect the outcome.
David M
@David M: it does work. Try this statement, it will yield 2.5M in `x`: `Decimal x = 1M * 5 / 2;` *(PS: I see now what you mean: it depends on the calculation and the order of execution, perhaps not always a good idea then)*.
Abel
But `1M * (5 / 2)` should not work - it will depend heavily on the structure of the actual calculation if it will work or not. It will probably work as long as the calculation is evaluated left to right and therefore the left operand is always a decimal.
Daniel Brückner
Maybe it should not, but it does work. Indeed, it is the order of execution. Add addition and it fails. It's simply the operator precedence. Lesson learned :)
Abel
Just tried it - `Console.WriteLine(1M * (5 / 2));` prints `2.0` as expected failing the 1M*-trick.
Daniel Brückner
Correct. The difference between your and my statement are the parentheses. See my further elaboration here: http://stackoverflow.com/questions/3245045/converting-double-to-decimal/3245327#3245327
Abel
+1  A: 

As the others already mentioned you have on both sides of your division an integer. So the result is also an integer (which will then implicit converted to a decimal for the left hand side). To solve this problem, one side of your division has to be an decimal, causing that the decimal division is taken. So simply try this line of code:

converterDecimalCoordinates.Latitude = CartesianCoordinates.LatitudeHours + (CartesianCoordinates.LatitudeMinutes + (CartesianCoordinates.LatitudeSeconds / 60)) / (decimal)60;
Oliver
+3  A: 

As a byproduct of my discussion with David M and Daniel Brückner under this answer and the partially wrong statement by myself under this answer by Adam, it has become clear that, sorry to say, all answers are only partially correct. What is happening is this:

// example (all  x, y, z ar ints):
Decimal d = x + y + z / 60M;

// is left to right evaluated as
Decimal d = x + y + (((Decimal) z) / 60M);

// when doing addition, this is what happens when you add integers and something else:
Decimal d = x + y + (int) (((Decimal) z) / 60M);

// which will yield a truncated result.

The result is: that just adding a 60M or 60.0 to the whole statement, as has been suggested, will not (or may not) yield the wanted result, depending on execution order of the statement and/or the existence of addition / subtraction, as is the case in the OP's question.

To fix this, follow Adam's advice and convert each addition / subtraction step to decimals, use decimals all along (not very clear) or place the calculation in a little function that takes decimals as parameters, forcing implicit conversion:

Decimal GetDecimalLatitude(Decimal latitudeHours, Decimal latitudeMinutes, Decimal latitudeSeconds)
{
    return latitudeHours + (latitudeMinutes + (latitudeSeconds / 60)) / 60;
}

which, as a bonus, is shorter and adds to readability. Call this with the following statement:

converterDecimalCoordinates.Latitude = GetDecimalLatitude(
    CartesianCoordinates.LatitudeHours, 
    CartesianCoordinates.LatitudeMinutes,
    CartesianCoordinates.LatitudeSeconds);
Abel
+1 for implicit casting // golf claps
Adam