Re^7: How to write testable command line script? (updated)
by AnomalousMonk (Archbishop) on Nov 26, 2018 at 16:36 UTC
|
This is no different from any decimal ...
But if I have the decimal number -345, this is
-(3*10^2 + 4*10^1 + 5*10^0)
or
-1 * (3*10^2 + 4*10^1 + 5*10^0)
or
-3*10^2 + -4*10^1 + -5*10^0
or
-3*10^2 - 4*10^1 - 5*10^0
and not
-3*10^2 + 4*10^1 + 5*10^0
and similarly for numbers represented in sexagesimal base.
Negative numbers are only permitted in the degree column.
Unfortunately, if one has a d/m/s tuple @dms = (3, 4, 5) one cannot negate it by writing -@dms. One has to negate each element:
@neg_dms = map -1*$_, @dms;
or (-3, -4, -5). IIUC, the notation -3° 4' 5" is equivalent to the tuple (-3°, -4', -5") analogously to decimal notation. Klaf is describing and giving an example of base-60 subtraction with borrowing, but that is not fundamentally different from base-10 subtraction.
Update 1: Added the "Negative numbers ..." quote before the second paragraph for clarification, and various other minor wording changes and additions.
Update 2: Here's the Klaf subtraction example in terms of normalizing everything to decimal arc-seconds, doing a decimal subtraction and reducing arc-seconds to a d/m/s tuple:
c:\@Work\Perl\monks\thechartist>perl -wMstrict -le
"sub dms_2_secs {
my ($degs, $mins, $secs) = @_;
;;
return $degs * 3600 + $mins * 60 + $secs;
}
;;
sub secs_2_dms {
use integer;
;;
my ($secs) = @_;
;;
my $degs = $secs / 3600; $secs %= 3600;
my $mins = $secs / 60; $secs %= 60;
;;
return $degs, $mins, $secs;
}
;;
my $klaf_arc_secs = dms_2_secs(83, 18, 21) - dms_2_secs(39, 41, 28);
my @klaf_dms = secs_2_dms($klaf_arc_secs);
print qq{(@klaf_dms)};
"
(43 36 53)
(Update: The Klaf addition example also works correctly.) (Update: Moreover, the reversed subtraction dms_2_secs(39, 41, 28) - dms_2_secs(83, 18, 21) yields the expected (-43 -36 -53) tuple.)
Give a man a fish: <%-{-{-{-<
| [reply] [d/l] [select] |
|
|
| [reply] |
|
|
... these are 2 separate ways of describing the same [angle] ...
There are an infinite number of ways to describe any angle. I see nothing inherently wrong with representing -0° 1' 0" (or 0°, -1', 0" in a different notation) as (-1, 59, 0) internally as long as this tuple (and all such "weird" tuples; there are many in the test sets) correctly reduce to whatever you define as a "standard" representation (and the aforementioned tuple does reduce to (0, -1, 0) as shown below). In the end, it's all a matter of the constraints you choose to impose on, e.g., notation and internal representation, user input, user output, etc., etc. These are all programmer choices.
c:\@Work\Perl\monks\thechartist>perl -wMstrict -le
"use TrigCalc qw(reduce);
;;
my @dms = reduce(-1, 59, 0);
print qq{(@dms)};
"
(0 -1 0)
That said, simplest is usually best, so my choice for the internal representation of this angle would be -60 arc-seconds (or maybe decimal degrees, or radians, or whatever).
Give a man a fish: <%-{-{-{-<
| [reply] [d/l] [select] |
|
|
|
|
So far, the reduce sub is consistent with all of the examples in his text. User input would accept negative in the minute and second fields (unlike some other calculators), it just won't provide output with negatives in those fields, which are appropriately carried to the next highest term, as is done in this calculator http://www.mathopenref.com/dmscalculator.html
| [reply] |
|
|
But with the http://www.mathopenref.com/dmscalculator.html calculator, if I enter 0° 0' 59" and then add -0° 1' 0", I get -0° 0' 1". Surely the only way that can happen is if -0° 1' 0" represents a negative angle of 0° 1' 0", i.e., an angle of -1'. (Understandably, the calculator won't let you enter angles like 0° -1' 0" or 0° 0' 60".)
Give a man a fish: <%-{-{-{-<
| [reply] [d/l] |
Re^7: How to write testable command line script?
by pryrt (Abbot) on Nov 26, 2018 at 17:17 UTC
|
update: AnomalousMonk said it way better than I did. Original left for historical purposes.
You've misunderstood. I am claiming you are representing negative angles incorrectly. After the subtraction, if you get -60 seconds, you should have the equivalent of -1min. -1min is represented canonically as -0° 1' 0", not as -1° 59' 0". The answer you list in Re^2: How to write testable command line script? of my $test_7 = [ 0, 0, -60]; my $answer_7 = [-1, 59, 0]; is wrong. Honestly, your format doesn't allow you to use the right answer, unless you know how to get perl to store the "-0" correctly (for example, using text), because it should be my $answer_7 = [-0, 1, 0];.
regarding normal base-10 subtraction, when you subtract 0.1 from 0.0, you do NOT borrow one from the ones column, making it -1; then have 10 in the tenths column, and subtract 1, leaving 9/10ths, or -1.9. 0.0-0.1 is obviously -0.1. What you are claiming in DMS is exactly equivalent to claiming that -60sec is -1° 59' 0".
0.0 Try borrowing: (0-1=-1).(0+10=10) => (-1).(10)
-0.1 -( 0).( 1)
==== ==========
-0.1 (-1).( 9) => -1.9
That's nonsensical, so obviously not the right method for subtracting a bigger from a smaller.
To subtract a bigger number (M) from a smaller number (S) to do a difference D=M-S, you have to do -(S-M)=D
So start with the S-M:
0.1
-0.0
====
0.1
Then negate the answer
-0.1
The same is true in other bases, like the base 60 of DMS:
0°00'00"
-0°00'60"
========
??°??'??"
You start by making the bigger the first,
0°00'60"
-0°00'00"
========
????????
Then simplify the top term and subtract
0°01'00"
-0°00'00"
========
0°01'00"
The negate:
-0°1'0"
However you calculate it, whether you do it in decimal degrees, or in DMS, the only difference between -60s and +60s should be the sign at the beginning. +60s is (0° 1' 0"), and -60s is -(0° 1' 0"), which gets simplified to -0° 1' 0", exactly like -(0.016) gets simplified to -0.016 -- the negative sign is placed next to the most significant digit, but implies the whole result is negative. -0.016deg converts to -(0.016deg + 0/60 + 0/3600) = -(0 + 1/60 + 0/3600) = -(0° 1' 0"). You do not interpret -0.016 = (-0) + (0/10) + (1/100) + (6/1000) -- because that math evaluates to +16/1000, which is wrong. You evaluate -0.016 as -(0.016) = -(0 + 0/10 + 1/100 + 6/1000) = -(16/1000), which is right; or, in the other direction (-16 thousandths) = -(16 thousandths) = -(1 hundredth + 6 thousandths) = -(zero ones + 0 tenths + 1 hundredths + 6 thousandths). In the same way, -60sec = -(60sec) = -(0 + 0/60 + 60/3600) = -(0 + 1/60 + 0/3600) = -(0° 1' 0") = -0° 1' 0". Negative Degress minutes seconds (-x°y'z") is not (-x°) + (y') + (z") = -x + y/60 + z/3600, it is -(x° + y' + z") -(x + y/60 + z/3600).
Have I said this in a way that makes my point more clear?
the resource https://books.google.com/books?id=YRNeBAAAQBAJ&lpg=PR1&dq=Albert%20Klaf's%20Trigonometry%20Refresher&pg=PA12#v=onepage&q&f=false did not show an example when the result is negative. I do not believe you are extending into negative results correctly.
(and linking works just fine to me: click on the LINK icon once you've navigated to the google books page, copy the URL, and paste it here between square brackets, like [https://books.google.com/books?id=YRNeBAAAQBAJ&lpg=PR1&dq=Albert%20Klaf's%20Trigonometry%20Refresher&pg=PA12#v=onepage&q&f=false]. | [reply] [d/l] [select] |
|
|
I appreciate your input, and I generally agree my representation is not optimal for extension, but I want to clarify what my algorithm does, because this is not a correct understanding of the representation.
The algorithm is based on complement arithmetic, which is why I didn't give it too much thought. The Japanese soroban is based upon it; ie. When subtracting 2 - 8, you add the complement of 8, in base 10 is 2, because 2+8 = 10. The problem of 2-8 becomes (2+2)-10 = -6. In base 60, we would add the complement of 60 and subtract a 1 from the column on the left, until we get to the degrees, which are in decimal format.
For the very reasons you outlined (Perl's representation of small negatives), I represent -0.1 as an equivalent polynomial (-1 + 9/10),
or in this case 0 -1 0 as (-1 + 59/60 + 0), with the denominator implied as (-1 59 0).
Algebraically, a + (-a) = 0. If (0 -1 0) + (0 1 0) = (0 0 0), then we must treat -1 59 0 as equivalent to 0 -1 0, because
(-1 59 0) + (0 1 0) = (-1 60 0) = (0 0 0).
| [reply] |
|
|
Okay, I understand better where you're coming from. For an internal representation, that's fine.
However, don't expect users to work in that same system for input and output: in my experience, people thinking in terms of DMS will usually be thinking in terms of London's longitude as -0°7′39″ = -(0°7′39″), not as = (-1°) + 52′ + 21″. If your program outputted -1 52 21, someone looking at a map of England would be forgiven for assuming you were printing the longitude of (... /me searches a map for an example location...) slightly west of Swindon, not the longitude of London -- and if I entered -1 52 21, I would expect your program to interpret that as being near Swindon, not London.
For internals, I'm now in agreement with AnomalousMonk's post, where it was recommended to do everything internally as arc-seconds. I now like a that a lot more than doing everything in decimal degrees: the fractions 1/60 and 1/3600 don't get represented well in internal binary floating-point notation, whereas -459 arc-seconds is stored quite simply as an integer; this also makes the internal angle representation more like the time representation (seconds since the epoch); I also think that would make all the internal math much simpler.
| [reply] |
|
|
Ah. I think I understand now. Thanks for catching my error. I have no idea why I didn't consider that initially. Back to the drawing board.
Edit: What do you think the best way of representing the degree:min:sec is? I am leaning to simply converting everything to seconds, doing the arithmetic, and then converting to deg:min:sec format as Anomalous Monk did. I planned on extending the calculator to accept decimals anyway. But if I wanted to accept integer input, what would be the best way of representing this situation of small negatives?
Storing the -0 as text does not appeal to me.
| [reply] |
|
|
If you want to avoid "-0", my order of prefence would be: 1) store it as decimal; 2) store it as [$sign, $deg, $min, $sec] where all four are integers, with sign taking on -1 for negative angles; 3) store it as [$deg, $min, $sec, $sign] (but I don't like that order as much).
For accepting integer input, I'd just only allow sign on the degrees... so if you wanted to allow "-0 1 0", that would be interpreted as -1min. Since "input" implies "text" in my mind, you can just check for (and strip) the sign using text processing, and then re-apply the sign once you are done:
$input = "-0 1 0";
my $sign = ($input =~ /^\s*-/) ? -1 : 1;
my ($d,$m,$s) = map abs, split / /, $input;
my $decdegrees = $sign * ($d + $m/60 + $s/3600);
print $decdegrees
... or maybe ... $input = "-0 1 0";
my ($sign, $d, $m, $s) = ($input =~ /(-{0,1})(\d+)\s+(\d+)\s+(\d+)/);
my $decdegrees = (($sign)?-1:1) * ($d + $m/60 + $s/3600);
print $decdegrees
.
update: fix missing code tags | [reply] [d/l] [select] |