# North by North by North by South East

Given a string of N, S, E and W, output a bearing (angle clockwise from North in degrees), correct to 5 decimal places.

In traditional compass notation, a string is made up of only 2 of these characters (like NNW or ESE). Here you must also accept strings that contain all 4 (like WNNNSE). Using only 2 symbols allows humans to intuitively understand the meaning. Allowing 4 symbols makes it horrible to read, but allows shorter ways of describing a bearing to a given accuracy.

(As pointed out in the comments by user2357112, it turns out you can prove that for any given bearing, the 4 symbol string will be exactly the same length as the 2 symbol string, so I've based this challenge on a false assumption. Hopefully this lack of a practical purpose doesn't detract from your enjoyment of the challenge...)

The exact method is described below, and is equivalent to the traditional notation (it expands on it rather than changing it).

# Input

• The input is a single string containing only the characters NESW.
• The input may be a sequence of characters if you prefer, provided this does not include any preprocessing. For example, taking a nested list [N, [E, [S, [W]]]] to help with the order of processing is not permitted.
• Taking different characters is not permitted. You may not take a string of 1234 instead of NESW.

# Output

• The output must be a decimal number or string representation of one (not a rational/fraction).
• Trailing zeros do not need to be displayed. If the bearing is 9.00000, then the output 9 also counts as correct to 5 decimal places.
• The output is in the range [0, 360). That is, including 0 but excluding 360.
• Correctness is checked by rounding the output to 5 decimal places. If the bearing is 0.000005, this rounds to 0.00001. Outputs 0.00001 and 0.000005 are both correct.
• Output in scientific notation for some inputs is acceptable. For example, 1e-5 instead of 0.00001.

# Conversion

• The single character compass points N, E, S, and W correspond to 0, 90, 180, and 270 degrees respectively.
• Prepending one of these to a string results in the bearing that bisects the bearing of the single character and the bearing of the original string.
• The closest of the two possible bisecting bearings is chosen, so that NE represents 45 degrees, not 225 degrees.
• This is unambiguous except where the angle to be bisected is 180 degrees. Therefore NS, SN, WE, and EW correspond to undefined bearings, and the input will never end in any of these. They may however appear anywhere else in the input string, as this causes no ambiguity.
• If the final two characters are identical, the final character will be redundant as the bisection will return the same bearing. Since this adds nothing to the notation, your code does not need to handle this. Therefore NN, EE, SS, and WW correspond to undefined bearings, and the input will never end in any of these. They may however appear anywhere else in the input string.

# Examples

N: 0
E: 90
S: 180
SE: halfway between S and E: 135
NSE: halfway between N and SE: 67.5
NNSE: halfway between N and NSE: 33.75
NNNSE: halfway between N and NNSE: 16.875
NNNNSE: halfway between N and NNNSE: 8.4375


# Test cases

A submission is only valid if it gives correct output for all of the test cases. Note that the test cases push to the limits of what can be handled with double precision. For languages that default to single precision, you will probably need to spend the bytes to specify double precision in order to get correct outputs.

Test case outputs are shown rounded to 5 decimal places, and also to arbitrary precision. Both are valid outputs.

WNE 337.5 337.5
WEN 337.5 337.5
WEWEWEWEWEWEWEWEWEWEWEN 330.00001 330.000007152557373046875
NESWNESWNESWNESWNESWNESWNESW 90 89.99999932944774627685546875
NNNNNNNNNNNNNNNNNNNNNNNE 0.00001 0.0000107288360595703125
NNNNNNNNNNNNNNNNNNNNNNNW 359.99999 359.9999892711639404296875
SNNNNNNNNNNNNNNNNNNNNNNNE 90.00001 90.00000536441802978515625
SNNNNNNNNNNNNNNNNNNNNNNNW 269.99999 269.99999463558197021484375


# Scoring

This is . The score is the length of the source code in bytes, and the shortest wins.

# Pedantry

I made the mistake of thinking that "North by North West" was a valid compass direction. A happy mistake, since it led to a challenge idea, but I then discovered from the Wikipedia page:

"The title of the Alfred Hitchcock 1959 movie, North by Northwest, is actually not a direction point on the 32-wind compass, but the film contains a reference to Northwest Airlines."

It also turns out that the method used for this challenge is only consistent with traditional compass points up to and including the 16 point compass. The 32-wind compass described on that page is subtly different and I have conveniently overlooked its existence for this challenge.

Finally, for anyone who thinks I should use "Southeast" instead of "South East", it seems to be a regional difference.

• WNNNSE <= what would be the output for this example entry at start of your post ? it sounds invalid for me, but it's hard to tell. Commented Nov 15, 2016 at 13:39
• @Tensibai For input WNNNSE the output would be 323.4375. See the example section for a walkthrough that would apply in the same way to this case. Commented Nov 15, 2016 at 13:40
• Is input like f(N,N,N,S,E) okay? Commented Nov 15, 2016 at 13:40
• @KarlNapf I've expanded the input section to clarify. If I understand correctly, your example input with multiple arguments seems equivalent to a sequence of characters, so it would be acceptable. Commented Nov 15, 2016 at 13:59
• "Allowing 4 symbols makes it horrible to read, but allows shorter ways of describing a bearing to a given accuracy." - are you sure about that? It seems like all inputs that describe the same bearing have the same length, since if you assign each bearing a dyadic rational from 0 to 1, a length-N string with N>1 always corresponds to a dyadic rational with denominator 2^(N+1) in lowest terms. Also, allowing more than 2 distinct letters in a bearing adds no expressive power; any bearing expressed with 3 or 4 letters can be expressed with 2. Commented Nov 15, 2016 at 23:35

# JavaScript (ES6), 84807874 72 bytes

Saved a byte thanks to @Titus, 1 thanks to @Neil

f=([c,...s],b="NESW".search(c))=>b*90-(s[0]?(b-=f(s)/90)-4*(b*b>4):0)*45


It took a while, but I think I've finally perfected the formula...

### Test snippet

f=([c,...s],b="NESW".search(c))=>b*90-(s[0]?(b-=f(s)/90)-4*(b*b>4):0)*45
g=(s,n)=>console.log('f("'+s+'"):',f(s),"expected:",n)

g("N", 0)
g("NE", 45)
g("E", 90)
g("SE", 135)
g("S", 180)
g("SW", 225)
g("W", 270)
g("NW", 315)
g("WNE", 337.5)
g("WEN", 337.5)
g("WEWEWEWEWEWEWEWEWEWEWEN", 330.00001)
g("NESWNESWNESWNESWNESWNESWNESWNESWNESWNESWNESWNESWNESW", 90)
g("NNNNNNNNNNNNNNNNNNNNNNNE", 0.00001)
g("NNNNNNNNNNNNNNNNNNNNNNNW", 359.99999)
g("SNNNNNNNNNNNNNNNNNNNNNNNE", 90.00001)
g("SNNNNNNNNNNNNNNNNNNNNNNNW", 269.99999)

### Explanation

Let's start with the simplest case: a single-char string. The result is simply its (0-indexed) position in the string NESW, multiplied by 90.

For a two-char string, the result lies halfway between the result of the first char and the result of the second. However, there's a catch: if the absolute difference between the two is greater than 180 (e.g. NW or WN), we must 180 to the angle so that it's not pointing the opposite direction.

For any longer string, the result lies halfway between the result of the first char and the result of the rest of the string. This can be generalized in the following way:

• If the input is a single char, return its index in the string NESW times 90.
• Otherwise, return the index of the first char in the string NESW times 45, plus half the result of the rest of the string; add an extra 180 if the absolute difference between the two is greater than 90.
• Excellent way to cutting the first character off the string! You can save one byte if you calculate with values divided by 45. Commented Nov 16, 2016 at 9:55
• @Titus I can save 2 bytes with that technique, thanks! Commented Nov 16, 2016 at 13:08
• search instead of indexOf to save you a byte.
– Neil
Commented Nov 16, 2016 at 13:47
• @Neil Thanks again! I managed to golf off three more by completely rearranging the equation. Commented Nov 16, 2016 at 16:04

## C#6, 226217207 185 bytes

using System.Linq;double N(string s){double b=f(s.Last());foreach(var c in s.Reverse()){b=(b+f(c)+(b-f(c)>2?4:f(c)-b>2?-4:0))/2;b=(b+4)%4;}return b*90;}int f(char x)=>"NESW".IndexOf(x);


Edit: -10 bytes by "borrowing" idea from ETHproductions's submission
-22 bytes thanks to @Titus

Ungolfed

// Call this method
double N(string s){
// Initialize bearing with last direction
double b=f(s.Last());
// Do backward. Doing last direction once more doesn't impact result
foreach(var c in s.Reverse()){
// Average current bearing with new bearing, adjusted with wrapping
b=(b+f(c)+(b-f(c)>2?4:f(c)-b>2?-4:0))/2;
// Make bearing back to range [0,4)
b=(b+4)%4;
}
// Change from "full circle = 4" unit to degree
return b*90;
}
// helper method to convert direction to bearing. This returns bearing with full circle = 4.
int f(char x)=>"NESW".IndexOf(x);

• I think you can make your range adjustment back to [0,360) shorter by using % Commented Nov 15, 2016 at 15:01
• @trichoplax Won´t that cut the decimals? Commented Nov 15, 2016 at 15:06
• Commented Nov 15, 2016 at 15:08
• Save 10 bytes with b=(b+360)%360; instead of b+=b>360?-360:b<0?360:0;. Save another 12 bytes with dividing everything by 90 and return b*90;. Commented Nov 16, 2016 at 10:07
• Here are 10 more bytes: merge the two assignments and remove braces: b=(b+f(c)+(b-f(c)>2?4:f(c)-b>2?-4:0)+8)/2%4; then distribute +8 to the ternary results b=(b+f(c)+(b-f(c)>2?12:f(c)-b>2?4:8))/2%4; Commented Nov 16, 2016 at 12:29

# PHP, 958886100127104 101 bytes

• -7 bytes with the null coalescing operator
• -2 bytes by not replacing N (and more, because that allows to put the translation to the loop head: N is truthy, but evaluates to 0 in the calculation.)
• +41 bytes for fixing the bisection (cough)
• -7 bytes directly and -16 indirectly inspired by @ETHproductions´ code
• -3 bytes by replacing strtr with one of my bit jugglings

for($i=strlen($s=$argv[1]);$i--;$p=($q+$p=$p??$q)/2+2*(abs($q-$p)>2))$q=ord($s[$i])/.8+3&3;echo$p*90;  This is officially the first time ever that I use the null coalescing operator. Run with -r. ## PHP 7.1 Negative string offsets in the upcoming PHP version will save 12 bytes: Replace strlen($s=$argv[1]) with 0 and $s with $argv[1]. Free bytes for (almost) everyone: • Calculating with 0,1,2,3 instead of 0,90,180,270 and multiplying the final result with 90 will save two bytes and probably allow further golfing. • There are some patterns in the ASCII codes of the characters. Try one of these in your language: • (a/2%6+2)%5 • a<87?a/2&3^3:3 or a/2&3^3*(a<87) • a&1?a&2|a/4&1:0 • a/.8-1&3 ## Python 3, 133 113 bytes Just improving on @L3viathan's answer because I just made this account and therefore can't make comments yet. d={"N":0,"E":.5,"S":1,"W":1.5} def B(s): b=d[s[-1]] for c in s[::-1]:b=(b+d[c])/2+(abs(b-d[c])>1) return b*180  • Welcome to Programming Puzzles & Code Golf, and nice improvement... Commented Nov 15, 2016 at 21:52 • I didn't see your answer, but Titus had a similar idea, plus I had another, I'm down to 98 now :) Commented Nov 16, 2016 at 12:55 # 05AB1E, 484237 32 bytes Saved 6 bytes thanks to Emigna. Saved 5 bytes thanks to Titus' idea to work on range [0,4[ and multiply by 90 at the end. Saved 5 bytes thanks to Adnan's mastery of ancient xor/modulo metamorphosis. So every angle is reduced from range [0,360[ to range [0,4[ throughout the execution. Result is then multiplied by 90 and displayed. Ç30^5%R¬U¦vXy+;DX-Ä0›2*+4%U}X90* It can be divided into two sequentially called subprograms. First program: convert input string into an array of the corresponding angles in range [0,4[ Ç Take the ascii value of all input characters 30^5% Dark ascii manipulation that yields [0,1,2,3] for [N,E,S,W] Now we have an array of integers in range [0,4[. Second program: actually compute the final angle R Reverse the array ¬ Take the first value (the last of the non-reversed array) U Pop it from the stack and set X to the same value ¦ Strip the first element v For each remaining element Xy+; Compute the average value between the leftmost value and X DX-Ä0› Push 1 if angular distance cast to integer is > 0 (i.e. if it is >= 1), 0 otherwise. It's equivalent to checking >= 90 degrees 2*+ Multiply by 2 (=2 if angular distance is >= 1 and 0 otherwise) and add it to the formerly computed average value. It's equivalent to multiplying by 180 4% Perform mod 4. It's equivalent to performing mod 360 U Store the result back to X } End for, mandatory if input has only one character X90* Push X*90 and implicitly display it  Try it online! Potential axes of golfing: • Not sure if that mod 4 is required (it would save 2 bytes). All test cases work without it, but maybe there exists a tricky case. A mathematical proof to either validate it or nullify it would be top-notch. • There is no implicit stuff besides displaying the result (closing quote marks, closing brackets). • Doesn't seem to give the requested result on the NNNNNNNNNNNNNNNNNNNNNNNE and SNNNNNNNNNNNNNNNNNNNNNNNE test cases. Commented Nov 16, 2016 at 10:27 • Weird. Now I do as well. I must have pasted wrong or something, sorry. You can shorten the code down to v"NESW"yk90*})R¬U¦vXy+;DX-Ä89›180*+360%U}X. Commented Nov 16, 2016 at 10:47 • Great explanation! It might be worth including a note that 89› actually means the integer part is greater than 89, which is equivalent to saying the complete number is greater than or equal to 90 (which still works fine because exactly 90 should never occur). Currently the comment in the explained code makes it sound like it's checking for greater than 89, whereas your code passes the test cases so is clearly correctly checking for greater than 90. Commented Nov 16, 2016 at 12:50 • I edited the explanation so, however I wrote "cast to integer" since I'm not sure how the operator › should behave towards negative floating point values. There is no problem here since it works on the absolute value, but I prefer not making too strong assumptions on the operator. Commented Nov 16, 2016 at 22:28 • You can replace v"NESW"yk}) with Ç30^5% :) Commented Nov 17, 2016 at 9:03 # Python 3, 146145117107979493 92 bytes f(s):u='NESW'.find(s[0])*90;return(u+f(s[1:]))/2+180*(abs(u-‌​f(s[1:]))>180)if s[1:]else u  Call f with the string. • You can't have the two ...0else they throw SyntaxErrors. Commented Nov 15, 2016 at 18:56 • @JonathanAllan What version of Python are you using? I'm on 3.5.2 and it works. Commented Nov 15, 2016 at 18:58 • I ran it on 3.3.3 - can you remove the space between else and - too? (can in 3.3.3) Commented Nov 15, 2016 at 19:17 • @JonathanAllan Yes I can! Thanks, that saves me another byte. Commented Nov 15, 2016 at 19:18 • @Titus d.find can, I had the exact idea a minute ago; see updated answer. Commented Nov 16, 2016 at 12:53 # C, 184 bytes double h(char c){return ((c=='E')+(c=='S')*2+(c=='W')*3);}double d(char*s){double f=h(*s);if(s[1]){double t=f;f=(f+d(s+1)/90)/2;if(((t-f)>1)||((f-t)>1))f+=2;if(f>=4)f-=4;}return f*90;}  Ungolfed // a helper function double direction_(char ch) { if (ch=='N') return 0.; else if (ch=='E') return 90.; else if (ch=='S') return 180.; else return 270.; } // this is the main function to call double direction(char* str) { double fAngle = direction_(str[0]); if (str[1]) { double tmp = fAngle + direction(str+1); if (tmp>=360.) tmp-=360.; tmp/=2; if (((tmp-fAngle)>90.) || ((tmp-fAngle)<-90.)) { // check if we need to take the "other side"; if the resulting angle is more than 90 degrees away, we took the wrong on if (tmp>=180.) tmp-=180.; else tmp+=180.; } fAngle = tmp; } return fAngle; }  • seems that using float, doesn't give you the precision needed. Commented Nov 15, 2016 at 16:23 • Welcome to PPCG! :D Commented Nov 15, 2016 at 16:51 • Won't the names of the functions conflict with each other (since the names are both d)? Commented Nov 16, 2016 at 6:18 • @qwerp, different signature (one takes char*, other takes only char) Commented Nov 16, 2016 at 8:41 • Function names are not name mangled in C, as they are in C++, so you need to rename one of them if you want it to be C. Commented Nov 17, 2016 at 14:41 ## R, 172 146 bytes z=rev((0:3*90)[match(scan(,""),c("N","E","S","W"))]);p=z[1];l=length(z);for(i in 2:l)p=(p+z[i])/2+(abs(p-z[i])>180)*180;if(l<2)p=z;sprintf("%f",p)  Ungolfed z=rev((0:3*90)[match(scan,""),c("N","E","S","W"))]); #1 p=z[1]; #2 l=length(z) #3 for(i in 2:l)p=(p+z[i])/2+(abs(p-z[i])>180)*180; #4 if(l<2)p=z #5 sprintf("%f",p) #6  Explained 1. Read input from stdin • Match input by index to c("N","E","S","W") • From matched indices: match to the vector of degrees 0:3*90 (instead of c(0,90,180,270)) • Reverse and store as z 2. Initialize p to the degree equivalent to last char in input 3. Store length of input as l 4. Iteratively, calculate the closest of the two possible bisecting bearings. 5. If only one input is given, set p to z 6. Format and print Try the test cases on R-fiddle (note that this is a function due to scan not working on R-fiddle) • Provided that the output is correct to 5 decimal places, you don't need to perform the rounding. From the challenge: Outputs 0.00001 and 0.000005 are both correct. So you should be able to save some bytes by not rounding Commented Nov 15, 2016 at 17:14 • @trichoplax I understand. Can the input also be a vector of string characters like. c("N","N","E") instead of "NNE"? This equivalent to a non-nested python list ["N","N","E"]. Commented Nov 15, 2016 at 17:19 • Yes. I intended "sequence" to be a general term to include things like arrays, vectors, lists, tuples. Commented Nov 15, 2016 at 17:24 • I think you can save 4 bytes if you divide everything by 90 and printf(p*90). Commented Nov 16, 2016 at 9:58 # Haskell, 109 105 103 bytes h=180 a#b|abs(a-b)<h=n|n>h=n-h|1>0=n+h where n=(a+b)/2 -- calculates the new "mean" on the cirlce f 'N'=0 -- translates characters to angles f 'E'=90 f 'S'=h f _=270 foldr1(#).map f -- traverses the whole string  Thanks for -2 byte @xnor! • The exhaustive list for f looks long, but I'm having trouble finding anything shorter. Closest I got was f c=90*until(\i->"NESW"!!i==c)(+1)0 (35). I think you can replace 'W' with _. – xnor Commented Nov 16, 2016 at 3:18 • Yeah I also expected there to be something shorter, but didn't find anything. Thanks for the _! Commented Nov 16, 2016 at 21:42 # Dyalog APL, 5545 38 bytes ### Solution Requires ⎕IO←0, which is default on many systems. Asks for direction. 360|÷○÷180×12○(+÷(|+))/¯12○○2÷⍨'NES'⍳⍞  ### Explanation Goes around the problem by converting each letter to a complex number 1∠θa + b · i, then doing a sum reduction from right-to-left (APL's forte) while normalizing at each step. The final θ is then converted to degrees and normalized to be within [0, 360): 'NES'⍳⍞ the indices of each input letter in "NES"; N→0, E→1, S→2, anything else→3 ○2÷⍨ convert to angles in radians; θ = π · x2 ¯12○ convert to complex numbers on the unit circle; ei · θ (...)/ reduce the list with... (i.e. insert the function between the elements of...) +÷(|+) ... the normalized sum; xn - 1 + xn|xn - 1 + xn| 12○ convert to angle; θ ÷○÷180× convert to degrees; 1π · 1180 · x 360| division remainder when divided by 360 TryAPL online! ### Anecdote If input and output was as orthogonal complex units, the entire solution would be just: (+÷(|+))/  The rest of the code is parsing input and formatting output. • I notice the test outputs do not match those in the challenge to 5 decimal places, making this invalid. Does Dyalog APL have the option of using double precision? Commented Nov 15, 2016 at 15:26 • @trichoplax Yes, ⎕FR←1287 makes use of 128 bit floats, but TryAPL doesn't allow it. – Adám Commented Nov 15, 2016 at 15:38 • I think anything greater than or equal to 64 bit floats should work (I've only tested in Python though). Does this mean you can make the code valid but it will then only work for people who have installed the language? Maybe you could show the full valid code for the score, and include the online version that doesn't quite have the required accuracy so people can see that the algorithm is correct. Commented Nov 15, 2016 at 15:42 • @trichoplax Actually, TryAPL uses double precision, but your test cases accumulate errors beyond 53 bits. – Adám Commented Nov 15, 2016 at 16:09 • If it can be shown that the difference is due to differences in interpretation of the IEEE 754 standard, that are still standard compliant, then I'll adjust the test cases to make sure both interpretations give the same result to 5 decimal places. I chose the test cases such that they gave the same result to 5 decimal places in Python for both float (double precision) and arbitrary precision decimals. I'll have a look into it. Commented Nov 15, 2016 at 16:29 # Common Lisp, 347 327 bytes Thanks to @Titus for taking off a few This can probably be golfed more, but at least it works (I think): (defun d(c)(if(eql c #\N)0(if(eql c #\E)1(if(eql c #\S)2(if(eql c #\W)3)))))(defun m(a b)(if(> a b)(rotatef a b))(if(<(+(- 4 b)a)(- b a))(+(/(+(- 4 b)a)2)b)(+(/(- b a)2)a)))(defun f(s)(let((c))(setf c(d(char s(1-(length s)))))(do((a)(p(-(length s)2)(1- p)))((< p 0))(setf a(char s p))(setf c(m(d a)c)))(format t"~5$"(* c 90))))


Usage:

* (f "WNE")
337.50000
NIL


Function d takes a character N, E, W, or S and returns the appropriate degree. Function m gets the approprate combined degree of two given directions. Function f iterates through the provided string, computes the appropriate degree, and prints it as a floating point.

• My LISP is rusty, but can dividing everything by 90 save 6 bytes? Commented Nov 16, 2016 at 10:01
• @Titus I think it would. I've realized some other improvements so I'll add this when I'm at my computer Commented Nov 16, 2016 at 13:08

# Befunge, 183181 175 bytes

>~#+:#25#%6*#/#2_$>5%4*:00p"Z}"4*:***20g#v_+2/00g10g-:8\0\-8+!v v5:+*:*:"d"/+55+5$_^#!:\p01/**:*4"}Z":p020<%**:*"(2Z"+**5*:*"0}"!<
>5>+#<%#56#58#:*#/+\#5:#5_$$,,,".">:#,_@  Try it online! Explanation This follows a similar algorithm to many of the other answers, only it's using fixed point calculations emulated with integers since Befunge doesn't support floating point. Thanks to @Titus for the ASCII-to-int routine.  ~ : 5 6*  _ while ((c = getchar()) > 30) // ends with any ctrl char or EOF > + 2 %6 / 2 push(c / 2 % 6 + 2) // partial conversion to int do { 5% dir = pop() % 5 // completes the conversion to int 4*:00p dir *= 4; lowres_dir = dir // used by the 180-flip calculation "Z}"4*:*** dir *= 22500000 // this is 90000000 / 4 20g_ if (!first_pass) { +2/ dir = (dir+last_dir)/2 // last_dir is second item on stack 00g10g- diff = lowres_dir - last_lowres_dir :8\0\-8+!! flip = diff>8 || -diff>8 "}0"*:*5**+ dir += flip * 180000000 // add 180 degrees if we need to flip "Z2("*:**% dir %= 360000000 // keep within the 360 degree range } 020p first_pass = false :"Z}"4*:**/10p last_lowres_dir = dir / 22500000 \ last_dir = dir // saved as second item on stack :!_ } while (!stack.empty())  pop() // this leaves the final dir on top 5+55+/ dir = (dir + 5)/10 // round down to 5 decimal places "d":*:*+ dir += 100000000 // add a terminating digit while (true) { // convert into chars on stack :55 + % 6 8 * +\ : _ push(dir%10+'0'); if (!dir) break > < 5 5 : /+ 5 5 dir /= 10 }$$\$                   pop() x 3                     // drop the chars we don't need
,,,                   putchar(pop()) x 3            // output first three chars
"."                   push('.')                     // add a decimal point
>:#,_@                while(c=pop()) putchar(c)     // output the remaining chars

• Does that just mean you need to emulate a larger fixed point type (more decimal places)? The test cases are designed to require double precision, which is no more than 17 significant figures (a maximum of 16 decimal places), and 14 decimal places might possibly be enough. Commented Nov 16, 2016 at 22:50

# APL (Dyalog Classic), 30 27 bytes

90×(4|2÷⍨+-4×2<∘|-)/'NES'⍳⎕


Try it online!