It’s usually convenient to store and process angles and times in some simple decimal value, but we often wish to display them in a conventional sexagesimal notation or allow users to enter values that way. So we need to be able to convert from 10.3 degrees to 10d 18′ 00″ or vice versa. There are a fair number of subtleties that lead to gotchas in naive code that performs these kinds of conversions.
First lets consider transformation from sexagesimal to decimal. Seems like it should be straightforward: We just parse three integers d, m and s and
dec = d+m/60 + s/3600.
Great. But what about
-10 30 30. That addition doesn’t work right. We need to make the minutes and seconds add to the magnitude of the degrees. How about
dec = abs(d) + m/60 + s/3600;
if (d < 0) dec = -dec;
That handles everything fine doesn't it? Not quite. What about
-00 30 30? Typically
-00 will be read the same as
0 so as soon as we naively converted the first string to integer we made an error. The code will misrepresent positions just south of the equator. I've done this myself and seen it in the wild more than once. To handle these values we need to explicitly look for the sign and if we find a - sign then invert the sign of our result. This is a really easy bug to miss since it only affects a small fraction of the sky.
So we need something like
sign = 1;
if (the coordinate string starts with a -) sign = -1;
dec = sign*(abs(d) + m/60 + s/3600);
to handle everything properly.
Any issues going the other way, converting from decimal to sexagesimal?
There are a few. There's the obvious counterpart to the the one we just handled.
E.g., if we have an input declination of -0.5, then trying
d = int(dec)
m = 60*int(dec-d)
isn't going to work. The variable d is going to be
0 and we've lost the sign. Bad things will happen. What we want to do is print out the sign first and then work on the absolute value
of the input angle.
And there is a more subtle issue that has to do with rounding...
Suppose we are looking at transforming 10.49999 degrees to sexagesimal.
Ok we have 10 degrees.
Next we see .49999*60 = 29.994 so we 29 minutes.
and .994*60 = 59.64 seconds.
So we could represent this as
10d 29' 59.64"
However we often want to format our results to some specified precision. E.g., suppose I want to output my data to arcsecond precision. I could try
10d 29' 59"
but that's silly we should be rounding to the nearest arcsecond. So we need to round up to
10d 29' 60"
Ooops... 60" isn't something I'm supposed to show. I need to go back and increment the arcminutes to get
10d 30' 00"
If the arcminutes had been 59 prior to the increment then I'd need to go back and increment the degrees... This is a bit of a pain but it needs to be done to format the data properly. If you are doing the longitude or right ascension, you may also want to deal with the special case of what happens when you round up to 360 degrees or 24 hours. Do you want to allow both 0H and 24H?
This kind of rounding issue occurs when we convert to decimal too, but it's handled by the language I/O libraries so we don't have to worry about it.
Rather than having code to respond to the increments when they happen we can convert the input value to a scaled integer early on and make sure the increments don't happen.
E.g., suppose we want to show to a precision of 0.1". We take the input angle (we've already dealt with the sign so we can assume it to be positive) and convert to units of the precision we want. E.g.,
decx = floor(36000*dec+0.5);
so we convert degrees to tenths of arcseconds by multiplying by 36000, then add 0.5 to this value and take the largest integer value equal to or less than the result. This process rounds to the nearest integer value corresponding to our input (assuming the input in positive).
Now we can use integer division and modulus operators to get the degrees and minutes and seconds. We'll need to insert the decimal point appropriately if we're displaying at fractions of seconds.
One final issue is what about objects just south of the equator? Do we want to allow both
00 00 00 and -00 00 00
where the second case arises when we've rounded a position south of but with 1/2" of the equator? We can keep them distinct or handle this special case, but we should probably do it consciously!