Seite 1 von 1

v2.93 BUG! - Places: angle conversion is flawed [FIXED ±]

Verfasst: 11.02.2016, 23:53
von Mathemagician
Dirk,

I noticed in the Navigator text for Joseph Becker (in the Demo.ahn file), the latitude - expressed as a decimal degrees - is incorrect (30' is always 0.5 degrees). In the Places Management dialog, the number is correct (if I remember), but displays wrongly in the Navigator. I am assuming the problem lies with the conversions between decimal degrees and degrees-minutes-seconds. Actually, from decimal degrees to DMS is not as simple as it appears on the surface.

I have a small Pascal test file (following) which demonstrates the routines necessary. I hope the code is simple enough to follow.

Also, in the Navigator the location is displayed as decimal degrees. I think that method is not commonly used anywhere - decimal degrees are used in computation only (as a rule).

Code: Alles auswählen

Program conversiontest;

VAR
{Note: functions require numeric values, string -> numeric may be required,
as well as numeric -> string for display, depending on your usage}

{variables for testing.  Using globals for clarity}
  deg : extended ; {decimal degrees}
  d : longint ; {degrees - could be negative for negative angles}
  m : longint ; {minutes}
  s : extended ; {seconds - might be fractions of a second, hence extended}

{------------------------------}

PROCEDURE dms2deg ; {convert degrees-minutes-seconds to decimal degrees}
VAR
  isneg : boolean ;
  temp : longint ;
BEGIN
  IF d < 0
    THEN isneg := true
    ELSE isneg := false ; {save sign}
  temp := abs (d) ;
  deg := temp+((m+(s/60))/60) ;
  IF isneg
    THEN deg := -deg
 END ; {dms2deg}

{------------------------------}

PROCEDURE deg2dms ; {convert decimal degrees to degrees-minutes-seconds}
{A bit trickier, due to "rollover" during conversion, i.e. if result yields
60 seconds, that is actually 1 minute + 0 seconds!}
VAR
  temp : extended ; {MUCH easier to work in seconds!}
  absdeg : extended ; {absolute value of input}
  isneg : boolean ;
BEGIN
  IF (deg < 0)
    THEN isneg := true
    ELSE isneg := false ;{save sign}
  absdeg := abs (deg) ; {only work with positive angles}
  d := trunc (absdeg) ; {get degrees}
  temp := absdeg * 3600 ; {convert absolute value to seconds}
  temp := temp - (d * 3600) ; {remainder, in seconds}
{Note: d is longint, else * 3600 could cause 'rollover' in 16-bit integer}
  m := trunc (temp/60) ; {get minutes}
  s := temp - (m * 60) ; {get remainder, in seconds and decimal seconds}
  IF isneg
    THEN d := -d ; {might be negative}
END ; {deg2dms}

{------------------------------}

BEGIN {Lets test it out!}
  d := 63 ;    {assign test data}
  m := 59 ;     {CANNOT be negative in the real world}
  s := 55.125 ; {CANNOT be negative in the real world}
  dms2deg ;
  write ('D:',d,'  ','M:',m,'  ','S:',s:0:3) ; {seconds to 3 decimals}
  writeln (' -> DEG:',deg:0:8) ; {decimal degrees - 8 decimals}
  writeln ;
  deg2dms ; {just use answer from previous conversion}
  write ('DEG:',deg:0:8) ;
  writeln (' -> D:',d,'  ','M:',m,'  ','S:',s:0:3) ; {seconds to 3 decimals}
END.
Here's a better way to do the same think (above is quite "klunky"):

Code: Alles auswählen

Program conversiontest1;

VAR
{Note: functions require numeric values; normal conversions to/from strings
       may be required, depending on your usage.  This approach is a little
       more 'modern', since it uses FUNCTIONS/PROCEDURES with parameters
       passed, rather than global variables. Also, the FRAC function is used
       to bypass a step or two. The routines also work for negative angles,
       as did the first routine ("conversiontest").}


{variables for testing}
  deg : extended ; {decimal degrees}
  d : longint ; {degrees - could be negative for negative angles}
  m : longint ; {minutes}
  s : extended ; {seconds - might be fractions of a second, hence extended}

{------------------------------}

FUNCTION dms2deg (_d,_m:longint; _s:extended): extended ; {DMS to dec. deg}
{Convert degrees-minutes-seconds to decimal_degrees. Always work with
positive input.}
VAR
  isneg : boolean ;
  temp : longint ;
  decdeg : extended ;
BEGIN
  IF _d < 0
    THEN isneg := true
    ELSE isneg := false ; {save sign}
  temp := abs (_d) ;
  decdeg := temp+((_m+(_s/60))/60) ;
  IF isneg
    THEN dms2deg := -decdeg {make negative}
    ELSE dms2deg := decdeg
 END ; {dms2deg}

{------------------------------}

PROCEDURE deg2dms (_deg:extended; VAR _d,_m:longint; VAR _s:extended) ;
{Convert decimal_degrees to degrees-minutes-seconds. Always work with
positive input.}
VAR
  isneg : boolean ;
  absdeg : extended ; {absolute value of input}
  temp : extended ; {remainders}
BEGIN
  IF (_deg < 0)
    THEN isneg := true
    ELSE isneg := false ; {save sign}
  absdeg := abs (_deg) ; {only work with positive angles}
  _d := trunc (absdeg) ; {get degrees}
  temp := frac (absdeg) * 60 ; {remainder = decimal_minutes}
  _m := trunc (temp) ; {get minutes}
  _s := frac (temp) * 60 ; {get seconds}
  IF isneg
    THEN _d := -d ; {return negative sign}
END ; {deg2dms}

{------------------------------}

BEGIN {Lets test it out!}
  d := 63 ;    {assign test data}
  m := 59 ;     {CANNOT be negative in the real world}
  s := 55.125 ; {CANNOT be negative in the real world}

  deg := dms2deg (d,m,s) ;
  write ('D:',d,'  ','M:',m,'  ','S:',s:0:3) ; {seconds to 3 decimals}
  writeln (' -> DEG:',deg:0:8) ; {decimal degrees - 8 decimals}
  writeln ;
{Lets make SURE the routine works, by zeroing the output first}
  d := 0 ;  m := 0 ;  s := 0 ;
{Then calculate d/m/s from "scratch"}
  deg2dms (deg,d,m,s) ; {just use answer from previous conversion}
  write ('DEG:',deg:0:8) ;
  writeln (' -> D:',d,'  ','M:',m,'  ','S:',s:0:3) {seconds to 3 decimals}
END.
Followup: I had a closer look at the value displayed in the Navigator's data box (for Joseph Becker)- the latitude is displayed as 51.516666 (for 51°30'). Converting back to DMS yields 51°30'60"! This is a classic rounding error in computers. I'm guessing the conversion routines currently use the INT function to grab the degrees or minutes, etc., but INT returns a real (from a real). All reals are encoded using base 2 techniques and are subject to tiny rounding errors - using INT (working with reals again) can produce a false result. For example, somewhere in your routine, the code may take the INT of 30.99999.... and returns 31, since the source is subject to error and the INT contributes to that error by again working in base 2 reals. I recommend having a look at my second routine - it should be fairly "bullet-proof", at least until the 5th (or higher) decimal place of seconds. The latitude and longitude can be stored internally as EXTENDED reals (10 bytes, less handling) then converted back to DMS when needed; there shouldn't be a problem with that. By the way, I tried deleting the Dortmund latitude in Places Management, then re-entering it - seemed to convert OK. But why does Places display (on opening the file) 51.5, but the Navigator box displays 51.56666?

- Mathemagician (Ruhestand professionellen Landvermesser)

P.S. In my career, we ALWAYS used Hewlett-Packard calculators and computers. Mostly (in the old days!) they had proprietary CPUs, working in BCD (binary coded decimal). NO rounding errors!

Verfasst: 13.02.2016, 11:58
von DirkB
Dear Allen,

this problem will be fixed in the next version.

- Dirk

Verfasst: 13.02.2016, 17:04
von Mathemagician
Dirk,

Great! I think this type of rounding error would probably come back, but only with certain values. No doubt the logic you have used is correct (works in Places Management, at least), but the INT routine (at least with Delphi/Pascal) is troublesome.

Verfasst: 21.02.2016, 16:51
von Mathemagician
Dirk,

Excellent! The Delphi computations are undoubtedly now correct for DEC -> DMS (ver. 2.94), but there is a small glitch in the display of the result. Seconds are being displayed to 0.1", but are being truncated, rather than rounded (to the nearest 1/10 of a second). For example, 49.12355278 gets displayed as 49° 7' 24.7", but it is actually calculated as 24.79" (should display as 24.8"). Yes, I know this is being fussy - it translates to only about 3m "on the ground"!