Saturday, April 09, 2016

Leaving Mr. Hypotenuse Out of the Loop
Blog Entry #364

We make today a brief, basic foray into trigonometry via the next CCC script, "Tangent". Written by T. Jay Tipps in March 1998, the Tangent script
(I) asks the user to input the lengths of the sides opposite (opp) and adjacent (adj) to an acute angle (θ) in a right triangle and then
(O) calculates and outputs the opp/adj tangent ratio for the angle.

The tangent.html page features a mostly functioning script demo (the script HTML contains a very small mistake that we'll correct below). The javagoodies.com/tangent.txt document holding the script code is gone but the JavaScript Goodies copy of the document is still live.

Deconstruction

The Tangent script is reasonably short and straightforward so let me give it to you in one go:

```<script language="javascript"> function tan( ) { var opp = window.prompt("Enter the measurement of the side opposite of the angle you are trying to find:"); var adj = window.prompt("Enter the measurement of the side adjacent to the angle you are trying to find:"); var tan = opp / adj; var a = window.alert("The answer is " + tan + ". This was solved using the formula Tan A=Opposite/Adjacent."); } function touse( ) { window.alert("For Tan enter the sides opposite and adjacent of the angle you are trying to find."); } </script> <form> <input type="button" value="The Tangent Ratio" onclick="tan( );"> <inpu type="button" value="To Use The Tangent Ratio" onclick="touse( );"> <!-- There's no </form> tag in the source, but there should be. -->```

Regarding the next-to-the-last line, `<inpu` is the aforementioned HTML mistake - it's in both the tangent.txt document and the tangent.html source - and you don't see a button on the tangent.html page: appending a `t` thereto brings the button to life, as you would expect. Clicking the button calls a touse( ) function that pops up a help message of sorts on an alert( ) box.

The preceding button is where the action is. Clicking the button calls a tan( ) function that
(1-2) pops up a first prompt( ) box that asks for an opp length and then a second prompt( ) box that asks for an adj length,
(3) divides opp by adj, and
(4) displays the quotient (tan) plus some explanatory text on an alert( ) box.

• That opp and adj are strings poses no problem for the `var tan = opp / adj;` division.

• No validation is provided for the user's inputs; tan values that run many places past the decimal point are not truncated.

• The tan( ) prompt( ) messages and the touse( ) alert( ) message imply that the script is going to determine the angle whose tangent is opp/adj. The script does not actually do that: it just calculates the opp/adj ratio. We'll get the angle momentarily, however.

• Unlike a prompt( ) command, an alert( ) command doesn't have a return value (more precisely, the alert( ) return is undefined) and therefore the tan alert( )'s `var a =` assignment is unnecessary/serves no purpose.

Looking for a certain angle

As for the angle you are trying to find, finding it is a cinch:

`var angle = Math.atan(tan) * 180 / Math.PI;`

The JavaScript Math object has an atan(x) method that gets the arctangent, in radians, of its x argument; the Math.atan(x) return can be converted to an angle in degrees via a (180°/π radians) conversion factor.

The Math object has a second method for getting an arctangent, atan2(y, x), that connects us to a Cartesian coordinate system - more here.

Pre-demo

The demo below incorporates coding techniques that were presented in the A grounded I/O section of Blog Entry #360 and the Demo considerations section of Blog Entry #362. Here's the skinny:

• Re the original HTML, I've traded in the `<form>` for a `<div id="tangentDiv">` and recast the `<input type="button">`s as `<button type="button">`s.

• The opp and adj lengths are entered into `<input type="text">`s vis-à-vis prompt( ) boxes: we access those `<input>`s via a getElementsByTagName("input") operation.

• User inputs are restricted to numbers in the range -100 < x < 100 and with no more than two post-decimal point digits.

tan and angle values having three or more post-decimal point digits are toFixed(2) at the hundredths place.

• The formatted tan and angle values are displayed in `<samp>`s below a button.

• Clicking the button calls a resetTangent( ) function that clears the `<input>` values and the `<samp>` textContents by setting them to empty strings.

Division by 0

Suppose the opp length is 5 and the adj length is 0. In this case, the `var tan = opp / adj;` division gives Infinity, which is fair enough as f(θ) = tan(θ) does in fact approach infinity as θ approaches 90° (π/2 rad). If both opp and adj are 0, however, then the tan return is NaN, which isn't very helpful, is it? One way to forestall the latter possibility is:

```var tangentInputs, opp, adj, tan, angle, tangentSamps; tangentInputs = document.getElementById("tangentDiv").getElementsByTagName("input"); opp = tangentInputs[0].value; adj = tangentInputs[1].value; if (opp === "0" && adj === "0") { window.alert("Please enter at least one nonzero value."); for (var i = 0; i < tangentInputs.length; i++) tangentInputs[i].value = ""; tangentInputs[0].focus( ); return; } else { /* ...Remaining vetting code... */ }```

If the if condition is true, then a troubleshooting alert( ) pops up, the opp and adj input fields are cleared, focus( ) is given to the opp input field, and the tan( ) function exits.

Negative number inputs

The opp and adj lengths are necessarily positive if we stay within the right triangle context, but we don't have to do that, now do we? Via the unit circle and the periodic definition of the tangent function (cf. the aforelinked tangent-graph.gif image if you didn't look at it earlier) we can effectively extend the Tangent script to any real-valued angle and thereby accept negative number inputs that correspond to Quadrant II-IV measurements in a Cartesian plane.

The Math.atan(x) return spans a -π/2 (-90°) → π/2 (90°) range. Given the trigonometric identities tan(π - θ) = -tan(θ), tan(θ + π) = +tan(θ), and tan(-θ) = -tan(θ), we can put the angle value for an obtuse, straight, or reflex angle in the 90° < θ < 360° range via:

```var tan = opp / adj; angle = Math.atan(tan) * 180 / Math.PI; if (Number(adj) < 0) angle += 180; else if (Number(opp) < 0) angle += 360; if (/\.\d{3,}\$/.test(angle)) angle = angle.toFixed(2); document.getElementById("angleSamp").textContent = angle + "\u00b0";```

• The Number( ) operations are there for the purpose of readability: it's not necessary to explicitly numberify opp and adj as the JavaScript engine will convert them to numbers automatically for the `< 0` comparisons.
\u00b0 is the Unicode escape sequence for a ° character.

We rotate angle counterclockwise
(a) by 180° for 90° < θ < 270° and
(b) by 360° for 270° ≤ θ < 360°.
For example, if we input opp = -5 and adj = 5 - a negative rise and a positive run puts us in Quadrant IV - then the initial angle, -45, is twirled around to 315. You could just leave the angle at -45, of course, but I prefer to express it as a reflex angle.

As per the tan(θ + 2π) = +tan(θ) trigonometric identity, 360° < θ angles have the same tangent values that their θ < 360° coterminal counterparts do; I will limit angle to the 0° ≤ θ < 360° range in the name of simplicity.

Arctangent alternative

We can also directly plug opp as a y coordinate and adj as an x coordinate into a Math.atan2(y, x) command so as to get the arctangent, in radians, of the opp/adj quotient. The Math.atan2(y, x) method covers all four Cartesian Quadrants: its return spans a 0π range for Quadrants I and II (and their x-axis boundary) and a 0 range for Quadrants III and IV (and their x-axis boundary). The code below positions angle in the 0° ≤ θ < 360° range:

```angle = Math.atan2(opp, adj) * 180 / Math.PI; if (Number(opp) < 0) angle += 360;```

Having said this, some of Mozilla's atan2(y, x) Examples involving signed 0 and Infinity arguments are confusing and they incline me to stick with Math.atan(x) arctangents.

Speaking of signed 0s - in the name of completeness - I would use a regular expressions-based

`if (/^(\+|-)?0\$/.test(opp) && /^(\+|-)?0\$/.test(adj))`

condition to fend off an opp = +0|-0 AND adj = +0|-0 combination of inputs, which would otherwise take us back to the 0/0 = NaN situation.

Demo