use v6;
use Test;
plan *;

=begin description

Basic tests for trigonometric functions.

=end description

# See also: L<"http://theory.cs.iitm.ernet.in/~arvindn/pi/"> :)
my $PI = 3.14159265358979323846264338327950288419716939937510;

# -- pi
is_approx(pi, $PI, "pi()");
is_approx(pi + 3, $PI + 3, "'pi() + 3' may drop its parentheses before +3");

# This class, designed to help simplify the tests, is very much in a transitional
# state.  But it works as well as the previous version at the moment.  I'm checking
# it in just to clean up my local build (and save a remote copy as I take my
# the machine it lives on vacation).  Should have more updates to this over the 
# next several days.  --colomon, Sept 3rd 2009.

class AngleAndResult
{
    has $.angle_in_degrees;
    has $.result;
    
    multi method new(Int $angle_in_degrees is copy, Num $result is copy) {
        self.bless(*, :$angle_in_degrees, :$result);
    }
    
    method complex($imaginary_part_in_radians, $base) {
        my $z_in_radians = $.angle_in_degrees.Num / 180.0 * pi + ($imaginary_part_in_radians)i; 
        given $base {
            when "degrees"     { $z_in_radians * 180.0 / pi; }
            when "radians"     { $z_in_radians; }
            when "gradians"    { $z_in_radians * 200.0 / pi; }
            when "revolutions" { $z_in_radians / (2.0 * pi); }
        }
    }
    
    method num($base) {
        given $base {
            when "degrees"     { $.angle_in_degrees.Num }
            when "radians"     { $.angle_in_degrees.Num / 180.0 * pi }
            when "gradians"    { $.angle_in_degrees.Num / 180.0 * 200.0 }
            when "revolutions" { $.angle_in_degrees.Num / 360.0 }
        }
    }
    
    method rat($base) {
        given $base {
            when "degrees"     { $.angle_in_degrees / 1 }
            when "radians"     { $.angle_in_degrees / 180 * (355 / 113) }
            when "gradians"    { $.angle_in_degrees * (200 / 180) }
            when "revolutions" { $.angle_in_degrees / 360 }
        }
    }
    
    method int($base) {
        given $base {
            when "degrees"     { $.angle_in_degrees }
        }
    }
}

my @sines = ( 
    AngleAndResult.new(-360, 0),
    AngleAndResult.new(135 - 360, 1/2*sqrt(2)),
    AngleAndResult.new(330 - 360, -0.5),
    AngleAndResult.new(0, 0),
    AngleAndResult.new(30, 0.5),
    AngleAndResult.new(45, 1/2*sqrt(2)),
    AngleAndResult.new(90, 1),
    AngleAndResult.new(135, 1/2*sqrt(2)),
    AngleAndResult.new(180, 0),
    AngleAndResult.new(225, -1/2*sqrt(2)),
    AngleAndResult.new(270, -1),
    AngleAndResult.new(315, -1/2*sqrt(2)),
    AngleAndResult.new(360, 0),
    AngleAndResult.new(30 + 360, 0.5),
    AngleAndResult.new(225 + 360, -1/2*sqrt(2)),
    AngleAndResult.new(720, 0)
);

my @cosines = @sines.map({ AngleAndResult.new($_.angle_in_degrees - 90, $_.result) });

# -- atan
# The basic form of atan (one argument) returns a value in ]-pi, pi[.
# Quadrants I, III
is_approx(atan(1)           / $PI * 180, 45, 'atan Q I & III - default');
is_approx(atan(1/3*sqrt(3)) / $PI * 180, 30, 'atan Q I & III - default');
is_approx(atan(sqrt(3))     / $PI * 180, 60, 'atan Q I & III - default');

is_approx(atan(1, 'degrees'), 45, 'atan Q I & III - degrees');
is_approx(atan(1/3*sqrt(3), 'degrees'), 30, 'atan Q I & III - degrees');
is_approx(atan(sqrt(3), 'degrees'), 60, 'atan Q I & III - degrees');

is_approx(atan(1, 'gradians'), 50, 'atan Q I & III - gradians');
is_approx(atan(1/3*sqrt(3), 'gradians'), 100/3, 'atan Q I & III - gradians');
is_approx(atan(sqrt(3), 'gradians'), 200/3, 'atan Q I & III - gradians');

is_approx(atan(1, 'radians')           / $PI * 180, 45, 'atan Q I & III - radians');
is_approx(atan(1/3*sqrt(3), 'radians') / $PI * 180, 30, 'atan Q I & III - radians');
is_approx(atan(sqrt(3), 'radians')     / $PI * 180, 60, 'atan Q I & III - radians');

is_approx(atan(1, 1), 1/8, 'atan Q I & III - revolutions');
is_approx(atan(1/3*sqrt(3), 1), 3/36, 'atan Q I & III - revolutions');
is_approx(atan(sqrt(3), 1), 1/6, 'atan Q I & III - revolutions');


# Quadrants II, IV
is_approx(atan(-1)           / $PI * 180, -45, 'atan Q II & IV - default');
is_approx(atan(-1/3*sqrt(3)) / $PI * 180, -30, 'atan Q II & IV - default');
is_approx(atan(-sqrt(3))     / $PI * 180, -60, 'atan Q II & IV - default');

is_approx(atan(-1, 'degrees'), -45, 'atan Q I & III - degrees');
is_approx(atan(-1/3*sqrt(3), 'degrees'), -30, 'atan Q I & III - degrees');
is_approx(atan(-sqrt(3), 'degrees'), -60, 'atan Q I & III - degrees');

is_approx(atan(-1, 'gradians'), -50, 'atan Q I & III - gradians');
is_approx(atan(-1/3*sqrt(3), 'gradians'), -100/3, 'atan Q I & III - gradians');
is_approx(atan(-sqrt(3), 'gradians'), -200/3, 'atan Q I & III - gradians');

is_approx(atan(-1, 'radians')           / $PI * 180, -45, 'atan Q II & IV - radians');
is_approx(atan(-1/3*sqrt(3), 'radians') / $PI * 180, -30, 'atan Q II & IV - radians');
is_approx(atan(-sqrt(3), 'radians')     / $PI * 180, -60, 'atan Q II & IV - radians');

is_approx(atan(-1, 1), -1/8, 'atan Q II & IV - revolutions');
is_approx(atan(-1/3*sqrt(3), 1), -3/36, 'atan Q II & IV - revolutions');
is_approx(atan(-sqrt(3), 1), -1/6, 'atan Q II & IV - revolutions');


is_approx(atan(Inf),   $PI / 2, 'arctan approaches pi/2 as x -> Inf (default)');
is_approx(atan(-Inf), -$PI / 2, 'arctan approaches -pi/2 as x-> -Inf (default)');

is_approx(atan(Inf, 'degrees'),   90, 'arctan approaches 90 as x -> Inf (degrees)');
is_approx(atan(-Inf, 'degrees'), -90, 'arctan approaches -90 as x-> -Inf (degrees)');

is_approx(atan(Inf, 'gradians'),   100, 'arctan approaches 100 as x -> Inf (gradians)');
is_approx(atan(-Inf, 'gradians'), -100, 'arctan approaches -100 as x-> -Inf (gradians)');

is_approx(atan(Inf, 'radians'),   $PI / 2, 'arctan approaches pi/2 as x -> Inf (radians)');
is_approx(atan(-Inf, 'radians'), -$PI / 2, 'arctan approaches -pi/2 as x-> -Inf (radians)');

is_approx(atan(Inf, 1),   1/4, 'arctan approaches 1/4 as x -> Inf (revolutions)');
is_approx(atan(-Inf, 1), -1/4, 'arctan approaches -1/4 as x-> -Inf (revolutions)' );


# S29: C<atan2> computes the arctangent of $y/$x, and
# **takes the quadrant into account**. The second argument is
# assumed to be 1 if it is not present.
# Quadrant I
is_approx(atan2(1, 1)           / $PI * 180, 45, 'atan2 - default (Q I)');
is_approx(atan2(1)              / $PI * 180, 45, 'atan2 - default (Q I)');
is_approx(atan2(1, sqrt(3))     / $PI * 180, 30, 'atan2 - default (Q I)');
is_approx(atan2(1, 1/3*sqrt(3)) / $PI * 180, 60, 'atan2 - default (Q I)');

is_approx(atan2(1, 1, 'degrees')          , 45, 'atan2 - degrees (Q I)');
is_approx(atan2(1, sqrt(3), 'degrees')    , 30, 'atan2 - degrees (Q I)');
is_approx(atan2(1, 1/3*sqrt(3), 'degrees'), 60, 'atan2 - degrees (Q I)');

is_approx(atan2(1, 1, 'gradians')          , 50, 'atan2 - gradians (Q I)');
is_approx(atan2(1, sqrt(3), 'gradians')    , 100/3, 'atan2 - gradians (Q I)');
is_approx(atan2(1, 1/3*sqrt(3), 'gradians'), 200/3, 'atan2 - gradians (Q I)');

is_approx(atan2(1, 1, 'radians')           / $PI * 180, 45, 'atan2 - radians (Q I)');
is_approx(atan2(1, sqrt(3), 'radians')     / $PI * 180, 30, 'atan2 - radians (Q I)');
is_approx(atan2(1, 1/3*sqrt(3), 'radians') / $PI * 180, 60, 'atan2 - radians (Q I)');

is_approx(atan2(1, 1, 1)          ,  1/8, 'atan2 - revolutions (Q I)');
is_approx(atan2(1, sqrt(3), 1)    , 3/36, 'atan2 - revolutions (Q I)');
is_approx(atan2(1, 1/3*sqrt(3), 1),  1/6, 'atan2 - revolutions (Q I)');


# Quadrant II
is_approx(atan2(1, -1)           / $PI * 180, 135, 'atan2 - default (Q II)');
is_approx(atan2(1, -1/3*sqrt(3)) / $PI * 180, 120, 'atan2 - default (Q II)');
is_approx(atan2(1, -sqrt(3))     / $PI * 180, 150, 'atan2 - default (Q II)');

is_approx(atan2(1, -1, 'degrees')          , 135, 'atan2 - degrees (Q II)');
is_approx(atan2(1, -1/3*sqrt(3), 'degrees'), 120, 'atan2 - degrees (Q II)');
is_approx(atan2(1, -sqrt(3), 'degrees')    , 150, 'atan2 - degrees (Q II)');

is_approx(atan2(1, -1, 'gradians')          , 150, 'atan2 - gradians (Q II)');
is_approx(atan2(1, -1/3*sqrt(3), 'gradians'), 133+1/3, 'atan2 - gradians (Q II)');
is_approx(atan2(1, -sqrt(3), 'gradians')    , 166+2/3, 'atan2 - gradians (Q II)');

is_approx(atan2(1, -1, 'radians')           / $PI * 180, 135, 'atan2 - radians (Q II)');
is_approx(atan2(1, -1/3*sqrt(3), 'radians') / $PI * 180, 120, 'atan2 - radians (Q II)');
is_approx(atan2(1, -sqrt(3), 'radians')     / $PI * 180, 150, 'atan2 - radians (Q II)');

is_approx(atan2(1, -1, 1)          ,  3/8, 'atan2 - revolutions (Q II)');
is_approx(atan2(1, -1/3*sqrt(3), 1),  1/3, 'atan2 - revolutions (Q II)');
is_approx(atan2(1, -sqrt(3), 1)    , 5/12, 'atan2 - revolutions (Q II)');


# Quadrant III
is_approx(atan2(-1, -1)           / $PI * 180 + 360, 225, 'atan2 - default (Q III)');
is_approx(atan2(-1, -sqrt(3))     / $PI * 180 + 360, 210, 'atan2 - default (Q III)');
is_approx(atan2(-1, -1/3*sqrt(3)) / $PI * 180 + 360, 240, 'atan2 - default (Q III)');

is_approx(atan2(-1, -1, 'degrees') + 360          , 225, 'atan2 - degrees (Q III)');
is_approx(atan2(-1, -sqrt(3), 'degrees') + 360    , 210, 'atan2 - degrees (Q III)');
is_approx(atan2(-1, -1/3*sqrt(3), 'degrees') + 360, 240, 'atan2 - degrees (Q III)');

is_approx(atan2(-1, -1, 'gradians') + 400          , 250, 'atan2 - gradians (Q III)');
is_approx(atan2(-1, -sqrt(3), 'gradians') + 400    , 233+1/3, 'atan2 - gradians (Q III)');
is_approx(atan2(-1, -1/3*sqrt(3), 'gradians') + 400, 266+2/3, 'atan2 - gradians (Q III)');

is_approx(atan2(-1, -1, 'radians')           / $PI * 180 + 360, 225, 'atan2 - radians (Q III)');
is_approx(atan2(-1, -sqrt(3), 'radians')     / $PI * 180 + 360, 210, 'atan2 - radians (Q III)');
is_approx(atan2(-1, -1/3*sqrt(3), 'radians') / $PI * 180 + 360, 240, 'atan2 - radians (Q III)');

is_approx(atan2(-1, -1, 1) + 1          , 225/360, 'atan2 - revolutions (Q III)');
is_approx(atan2(-1, -sqrt(3), 1) + 1    , 210/360, 'atan2 - revolutions (Q III)');
is_approx(atan2(-1, -1/3*sqrt(3), 1) + 1, 240/360, 'atan2 - revolutions (Q III)');


# Quadrant IV
is_approx(atan2(-1, 1)           / $PI * 180 + 360, 315, 'atan2 - default (Q IV)');
is_approx(atan2(-1)              / $PI * 180 + 360, 315, 'atan2 - default (Q IV)');
is_approx(atan2(-1, sqrt(3))     / $PI * 180 + 360, 330, 'atan2 - default (Q IV)');
is_approx(atan2(-1, 1/3*sqrt(3)) / $PI * 180 + 360, 300, 'atan2 - default (Q IV)');

is_approx(atan2(-1, 1, 'degrees') + 360          , 315, 'atan2 - degrees (Q IV)');
is_approx(atan2(-1, sqrt(3), 'degrees') + 360    , 330, 'atan2 - degrees (Q IV)');
is_approx(atan2(-1, 1/3*sqrt(3), 'degrees') + 360, 300, 'atan2 - degrees (Q IV)');

is_approx(atan2(-1, 1, 'gradians') + 400          , 350, 'atan2 - gradians (Q IV)');
is_approx(atan2(-1, sqrt(3), 'gradians') + 400    , 366+2/3, 'atan2 - gradians (Q IV)');
is_approx(atan2(-1, 1/3*sqrt(3), 'gradians') + 400, 333+1/3, 'atan2 - gradians (Q IV)');

is_approx(atan2(-1, 1, 'radians')           / $PI * 180 + 360, 315, 'atan2 - radians (Q IV)');
is_approx(atan2(-1, sqrt(3), 'radians')     / $PI * 180 + 360, 330, 'atan2 - radians (Q IV)');
is_approx(atan2(-1, 1/3*sqrt(3), 'radians') / $PI * 180 + 360, 300, 'atan2 - radians (Q IV)');

is_approx(atan2(-1, 1, 1) + 1          , 315/360, 'atan2 - revolutions (Q IV)');
is_approx(atan2(-1, sqrt(3), 1) + 1    , 330/360, 'atan2 - revolutions (Q IV)');
is_approx(atan2(-1, 1/3*sqrt(3), 1) + 1, 300/360, 'atan2 - revolutions (Q IV)');


# -- sin, cos, tan
	    
my %official_base = (
    "radians" => "radians",
    "gradians" => "gradians", 
    "degrees" => "degrees",
    "revolutions" => 1
);

# SHOULD BE IN COMPLEX.PM!
# but that doesn't work for some reason...

# For some reason, the default values mechanism is giving trouble here.
# So write this function out twice for now.

multi sub sin(Complex $a)
{
    $a.sin;
}

multi sub sin(Complex $a, $base)
{
    # Doing it this way allows us to bypass the default value mechanism
    ($a.re!to-radians($base) + $a.im!to-radians($base) * 1i).sin;
}

multi sub cos(Complex $a)
{
    $a.cos;
}

multi sub cos(Complex $a, $base)
{
    # Doing it this way allows us to bypass the default value mechanism
    ($a.re!to-radians($base) + $a.im!to-radians($base) * 1i).cos;
}

# END SHOULD BE IN COMPLEX.PM!

# sin
		
for @sines -> $angle
{
    my $sine = $angle.result;
    
    # sin(Num)
	is_approx(sin($angle.num("radians")), $sine, 
	          "sin(Num) - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(sin($angle.num($base), %official_base{$base}), $sine, 
	              "sin(Num) - {$angle.num($base)} $base");
	}
	              
    # Num.sin tests
    is_approx($angle.num("radians").sin, $sine, 
              "Num.sin - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx($angle.num($base).sin(%official_base{$base}), $sine, 
	              "Num.sin - {$angle.num($base)} $base");
	}
	
	# sin(Rat)
	is_approx(sin($angle.rat("radians")), $sine, 
	          "sin(Rat) - {$angle.rat('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(sin($angle.rat($base), %official_base{$base}), $sine, 
	              "sin(Rat) - {$angle.rat($base)} $base");
	}
	              
    # Rat.sin tests
    is_approx($angle.rat("radians").sin, $sine, 
              "Rat.sin - {$angle.rat('radians')} default");
	for %official_base.keys -> $base {
	    is_approx($angle.rat($base).sin(%official_base{$base}), $sine, 
	              "Rat.sin - {$angle.rat($base)} $base");
	}

    # sin(Int)
    is_approx(sin($angle.int("degrees"), %official_base{"degrees"}), $sine, 
              "sin(Int) - {$angle.int('degrees')} degrees");
    is_approx($angle.int('degrees').sin(%official_base{'degrees'}), $sine, 
              "Int.sin - {$angle.int('degrees')} degrees");

    # Complex tests
    my Complex $zp0 = $angle.complex(0.0, "radians");
    my Complex $sz0 = $sine + 0i;
    my Complex $zp1 = $angle.complex(1.0, "radians");
    my Complex $sz1 = (exp($zp1 * 1i) - exp(-$zp1 * 1i)) / 2i;
    my Complex $zp2 = $angle.complex(2.0, "radians");
    my Complex $sz2 = (exp($zp2 * 1i) - exp(-$zp2 * 1i)) / 2i;
    
    # sin(Complex) tests
    is_approx(sin($zp0), $sz0, "sin(Complex) - $zp0 default");
    is_approx(sin($zp1), $sz1, "sin(Complex) - $zp1 default");
    is_approx(sin($zp2), $sz2, "sin(Complex) - $zp2 default");
    
    for %official_base.keys -> $base {
        my Complex $z = $angle.complex(0.0, $base);
        is_approx(sin($z, %official_base{$base}), $sz0, "sin(Complex) - $z $base");
        
        $z = $angle.complex(1.0, $base);
        is_approx(sin($z, %official_base{$base}), $sz1, "sin(Complex) - $z $base");
        
        $z = $angle.complex(2.0, $base);
        is_approx(sin($z, %official_base{$base}), $sz2, "sin(Complex) - $z $base");
    }
    
    # Complex.sin tests
    is_approx($zp0.sin, $sz0, "Complex.sin - $zp0 default");
    is_approx($zp1.sin, $sz1, "Complex.sin - $zp1 default");
    is_approx($zp2.sin, $sz2, "Complex.sin - $zp2 default");
              
	for %official_base.keys -> $base {
        my Complex $z = $angle.complex(0.0, $base);
        #?rakudo skip "Complex.sin plus base doesn't work yet"
        is_approx($z.sin(%official_base{$base}), $sz0, "Complex.sin - $z $base");
        
        $z = $angle.complex(1.0, $base);
        #?rakudo skip "Complex.sin plus base doesn't work yet"
        is_approx($z.sin(%official_base{$base}), $sz1, "Complex.sin - $z $base");
        
        $z = $angle.complex(2.0, $base);
        #?rakudo skip "Complex.sin plus base doesn't work yet"
        is_approx($z.sin(%official_base{$base}), $sz2, "Complex.sin - $z $base");
    }
}

is(sin(Inf), NaN, "sin - default");
is(sin(-Inf), NaN, "sin - default");
for %official_base.keys -> $base
{
    is(sin(Inf,  %official_base{$base}), NaN, "sin - $base");
    is(sin(-Inf, %official_base{$base}), NaN, "sin - $base");
}

# cos
		
for @cosines -> $angle
{
    my $cosine = $angle.result;
    
    # cos(Num)
	is_approx(cos($angle.num("radians")), $cosine, 
	          "cos(Num) - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(cos($angle.num($base), %official_base{$base}), $cosine, 
	              "cos(Num) - {$angle.num($base)} $base");
	}
	              
    # Num.cos tests
    is_approx($angle.num("radians").cos, $cosine, 
              "Num.cos - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx($angle.num($base).cos(%official_base{$base}), $cosine, 
	              "Num.cos - {$angle.num($base)} $base");
	}
	
	# cos(Rat)
	is_approx(cos($angle.rat("radians")), $cosine, 
	          "cos(Rat) - {$angle.rat('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(cos($angle.rat($base), %official_base{$base}), $cosine, 
	              "cos(Rat) - {$angle.rat($base)} $base");
	}
	              
    # Rat.cos tests
    is_approx($angle.rat("radians").cos, $cosine, 
              "Rat.cos - {$angle.rat('radians')} default");
	for %official_base.keys -> $base {
	    is_approx($angle.rat($base).cos(%official_base{$base}), $cosine, 
	              "Rat.cos - {$angle.rat($base)} $base");
	}

    # cos(Int)
    is_approx(cos($angle.int("degrees"), %official_base{"degrees"}), $cosine, 
              "cos(Int) - {$angle.int('degrees')} degrees");
    is_approx($angle.int('degrees').cos(%official_base{'degrees'}), $cosine, 
              "Int.cos - {$angle.int('degrees')} degrees");

    # Complex tests
    my Complex $zp0 = $angle.complex(0.0, "radians");
    my Complex $sz0 = $cosine + 0i;
    my Complex $zp1 = $angle.complex(1.0, "radians");
    my Complex $sz1 = (exp($zp1 * 1i) + exp(-$zp1 * 1i)) / 2.0;
    my Complex $zp2 = $angle.complex(2.0, "radians");
    my Complex $sz2 = (exp($zp2 * 1i) + exp(-$zp2 * 1i)) / 2.0;
    
    # cos(Complex) tests
    is_approx(cos($zp0), $sz0, "cos(Complex) - $zp0 default");
    is_approx(cos($zp1), $sz1, "cos(Complex) - $zp1 default");
    is_approx(cos($zp2), $sz2, "cos(Complex) - $zp2 default");
    
    for %official_base.keys -> $base {
        my Complex $z = $angle.complex(0.0, $base);
        is_approx(cos($z, %official_base{$base}), $sz0, "cos(Complex) - $z $base");
        
        $z = $angle.complex(1.0, $base);
        is_approx(cos($z, %official_base{$base}), $sz1, "cos(Complex) - $z $base");
        
        $z = $angle.complex(2.0, $base);
        is_approx(cos($z, %official_base{$base}), $sz2, "cos(Complex) - $z $base");
    }
    
    # Complex.cos tests
    is_approx($zp0.cos, $sz0, "Complex.cos - $zp0 default");
    is_approx($zp1.cos, $sz1, "Complex.cos - $zp1 default");
    is_approx($zp2.cos, $sz2, "Complex.cos - $zp2 default");
              
	for %official_base.keys -> $base {
        my Complex $z = $angle.complex(0.0, $base);
        #?rakudo skip "Complex.cos plus base doesn't work yet"
        is_approx($z.cos(%official_base{$base}), $sz0, "Complex.cos - $z $base");
        
        $z = $angle.complex(1.0, $base);
        #?rakudo skip "Complex.cos plus base doesn't work yet"
        is_approx($z.cos(%official_base{$base}), $sz1, "Complex.cos - $z $base");
        
        $z = $angle.complex(2.0, $base);
        #?rakudo skip "Complex.cos plus base doesn't work yet"
        is_approx($z.cos(%official_base{$base}), $sz2, "Complex.cos - $z $base");
    }
}

is(cos(Inf), NaN, "cos - default");
is(cos(-Inf), NaN, "cos - default");
for %official_base.keys -> $base
{
    is(cos(Inf,  %official_base{$base}), NaN, "cos - $base");
    is(cos(-Inf, %official_base{$base}), NaN, "cos - $base");
}

# tan
for @sines -> $angle
{
    next if abs(cos($angle.num("radians"))) < 1e-10; 
    my $tan = sin($angle.num("radians")) / cos($angle.num("radians"));
    
    # tan(Num)
	is_approx(tan($angle.num("radians")), $tan, 
	          "tan(Num) - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(tan($angle.num($base), %official_base{$base}), $tan, 
	              "tan(Num) - {$angle.num($base)} $base");
	}
	              
    # Num.tan tests
    is_approx($angle.num("radians").tan, $tan, 
              "Num.tan - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx($angle.num($base).tan(%official_base{$base}), $tan, 
	              "Num.tan - {$angle.num($base)} $base");
	}
	
	# tan(Rat)
	is_approx(tan($angle.rat("radians")), $tan, 
	          "tan(Rat) - {$angle.rat('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(tan($angle.rat($base), %official_base{$base}), $tan, 
	              "tan(Rat) - {$angle.rat($base)} $base");
	}
	              
    # Rat.tan tests
    is_approx($angle.rat("radians").tan, $tan, 
              "Rat.tan - {$angle.rat('radians')} default");
	for %official_base.keys -> $base {
	    is_approx($angle.rat($base).tan(%official_base{$base}), $tan, 
	              "Rat.tan - {$angle.rat($base)} $base");
	}

    # tan(Int)
    is_approx(tan($angle.int("degrees"), %official_base{"degrees"}), $tan, 
              "tan(Int) - {$angle.int('degrees')} degrees");
    is_approx($angle.int('degrees').tan(%official_base{'degrees'}), $tan, 
              "Int.tan - {$angle.int('degrees')} degrees");
}

is(tan(Inf), NaN, "tan - default");
is(tan(-Inf), NaN, "tan - default");
for %official_base.keys -> $base
{
    is(tan(Inf,  %official_base{$base}), NaN, "tan - $base");
    is(tan(-Inf, %official_base{$base}), NaN, "tan - $base");
}


# cotan
for @sines -> $angle
{
    next if abs(sin($angle.num("radians"))) < 1e-10; 
    my $cotan = cos($angle.num("radians")) / sin($angle.num("radians"));
    
    # cotan(Num)
	is_approx(cotan($angle.num("radians")), $cotan, 
	          "cotan(Num) - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(cotan($angle.num($base), %official_base{$base}), $cotan, 
	              "cotan(Num) - {$angle.num($base)} $base");
	}
	              
    # Num.cotan tests
    is_approx($angle.num("radians").cotan, $cotan, 
              "Num.cotan - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx($angle.num($base).cotan(%official_base{$base}), $cotan, 
	              "Num.cotan - {$angle.num($base)} $base");
	}
	
	# cotan(Rat)
	is_approx(cotan($angle.rat("radians")), $cotan, 
	          "cotan(Rat) - {$angle.rat('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(cotan($angle.rat($base), %official_base{$base}), $cotan, 
	              "cotan(Rat) - {$angle.rat($base)} $base");
	}
	              
    # Rat.cotan tests
    is_approx($angle.rat("radians").cotan, $cotan, 
              "Rat.cotan - {$angle.rat('radians')} default");
	for %official_base.keys -> $base {
	    is_approx($angle.rat($base).cotan(%official_base{$base}), $cotan, 
	              "Rat.cotan - {$angle.rat($base)} $base");
	}

    # cotan(Int)
    is_approx(cotan($angle.int("degrees"), %official_base{"degrees"}), $cotan, 
              "cotan(Int) - {$angle.int('degrees')} degrees");
    is_approx($angle.int('degrees').cotan(%official_base{'degrees'}), $cotan, 
              "Int.cotan - {$angle.int('degrees')} degrees");
}

is(cotan(Inf), NaN, "cotan - default");
is(cotan(-Inf), NaN, "cotan - default");
for %official_base.keys -> $base
{
    is(cotan(Inf,  %official_base{$base}), NaN, "cotan - $base");
    is(cotan(-Inf, %official_base{$base}), NaN, "cotan - $base");
}


# sec
for @cosines -> $angle
{
    next if $angle.result == 0.0;
    my $sec = 1.0 / $angle.result;
    
    # sec(Num)
	is_approx(sec($angle.num("radians")), $sec, 
	          "sec(Num) - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(sec($angle.num($base), %official_base{$base}), $sec, 
	              "sec(Num) - {$angle.num($base)} $base");
	}
	              
    # Num.sec tests
    is_approx($angle.num("radians").sec, $sec, 
              "Num.sec - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx($angle.num($base).sec(%official_base{$base}), $sec, 
	              "Num.sec - {$angle.num($base)} $base");
	}
	
	# sec(Rat)
	is_approx(sec($angle.rat("radians")), $sec, 
	          "sec(Rat) - {$angle.rat('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(sec($angle.rat($base), %official_base{$base}), $sec, 
	              "sec(Rat) - {$angle.rat($base)} $base");
	}
	              
    # Rat.sec tests
    is_approx($angle.rat("radians").sec, $sec, 
              "Rat.sec - {$angle.rat('radians')} default");
	for %official_base.keys -> $base {
	    is_approx($angle.rat($base).sec(%official_base{$base}), $sec, 
	              "Rat.sec - {$angle.rat($base)} $base");
	}

    # sec(Int)
    is_approx(sec($angle.int("degrees"), %official_base{"degrees"}), $sec, 
              "sec(Int) - {$angle.int('degrees')} degrees");
    is_approx($angle.int('degrees').sec(%official_base{'degrees'}), $sec, 
              "Int.sec - {$angle.int('degrees')} degrees");
}

is(sec(Inf), NaN, "sec - default");
is(sec(-Inf), NaN, "sec - default");
for %official_base.keys -> $base
{
    is(sec(Inf,  %official_base{$base}), NaN, "sec - $base");
    is(sec(-Inf, %official_base{$base}), NaN, "sec - $base");
}


# cosec
for @sines -> $angle
{
    next if $angle.result == 0.0;
    my $cosec = 1.0 / $angle.result;
    
    # cosec(Num)
	is_approx(cosec($angle.num("radians")), $cosec, 
	          "cosec(Num) - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(cosec($angle.num($base), %official_base{$base}), $cosec, 
	              "cosec(Num) - {$angle.num($base)} $base");
	}
	              
    # Num.cosec tests
    is_approx($angle.num("radians").cosec, $cosec, 
              "Num.cosec - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx($angle.num($base).cosec(%official_base{$base}), $cosec, 
	              "Num.cosec - {$angle.num($base)} $base");
	}
	
	# cosec(Rat)
	is_approx(cosec($angle.rat("radians")), $cosec, 
	          "cosec(Rat) - {$angle.rat('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(cosec($angle.rat($base), %official_base{$base}), $cosec, 
	              "cosec(Rat) - {$angle.rat($base)} $base");
	}
	              
    # Rat.cosec tests
    is_approx($angle.rat("radians").cosec, $cosec, 
              "Rat.cosec - {$angle.rat('radians')} default");
	for %official_base.keys -> $base {
	    is_approx($angle.rat($base).cosec(%official_base{$base}), $cosec, 
	              "Rat.cosec - {$angle.rat($base)} $base");
	}

    # cosec(Int)
    is_approx(cosec($angle.int("degrees"), %official_base{"degrees"}), $cosec, 
              "cosec(Int) - {$angle.int('degrees')} degrees");
    is_approx($angle.int('degrees').cosec(%official_base{'degrees'}), $cosec, 
              "Int.cosec - {$angle.int('degrees')} degrees");
}

is(cosec(Inf), NaN, "cosec - default");
is(cosec(-Inf), NaN, "cosec - default");
for %official_base.keys -> $base
{
    is(cosec(Inf,  %official_base{$base}), NaN, "cosec - $base");
    is(cosec(-Inf, %official_base{$base}), NaN, "cosec - $base");
}


# asin
is_approx(asin(0),            0, 'asin - default');
#?pugs 2 todo 'feature'
is_approx(asin(1/2*sqrt(2)),  1/4*$PI, 'asin - default');
is_approx(asin(1),            2/4*$PI, 'asin - default');

is_approx(asin(0, 'degrees'),            0, 'asin - degrees');
is_approx(asin(1/2*sqrt(2), 'degrees'), 45, 'asin - degrees');
is_approx(asin(1, 'degrees'),           90, 'asin - degrees');

is_approx(asin(0, 'gradians'),             0, 'asin - gradians');
is_approx(asin(1/2*sqrt(2), 'gradians'),  50, 'asin - gradians');
is_approx(asin(1, 'gradians'),           100, 'asin - gradians');

is_approx(asin(0, 'radians'),           0/4*$PI, 'asin - radians');
is_approx(asin(1/2*sqrt(2), 'radians'), 1/4*$PI, 'asin - radians');
is_approx(asin(1, 'radians'),           2/4*$PI, 'asin - radians');

is_approx(asin(0, 1),           0/8, 'asin - revolutions');
is_approx(asin(1/2*sqrt(2), 1), 1/8, 'asin - revolutions');
is_approx(asin(1, 1),           2/8, 'asin - revolutions');


# acos
#?pugs 2 todo 'feature'
is_approx(acos(0),           2/4*$PI, 'acos - default');
is_approx(acos(1/2*sqrt(2)), 1/4*$PI, 'acos - default');
is_approx(acos(1),           0/4*$PI, 'acos - default');

is_approx(acos(0, 'degrees'),           90, 'acos - degrees');
is_approx(acos(1/2*sqrt(2), 'degrees'), 45, 'acos - degrees');
is_approx(acos(1, 'degrees'),            0, 'acos - degrees');

is_approx(acos(0, 'gradians'),           100, 'acos - gradians');
is_approx(acos(1/2*sqrt(2), 'gradians'),  50, 'acos - gradians');
is_approx(acos(1, 'gradians'),             0, 'acos - gradians');

is_approx(acos(0, 'radians'),            2/4*$PI, 'acos - radians');
is_approx(acos(1/2*sqrt(2), 'radians'), 1/4*$PI, 'acos - radians');
is_approx(acos(1, 'radians'),            0/4*$PI, 'acos - radians');

is_approx(acos(0, 1),           2/8, 'acos - revolutions');
is_approx(acos(1/2*sqrt(2), 1), 1/8, 'acos - revolutions');
is_approx(acos(1, 1),           0/8, 'acos - revolutions');


# asec
is_approx(asec(-1.5707963267949), 2.26090341816943, 'asec - default');
is_approx(asec(1.5707963267949), 0.880689235420358, 'asec - default');

is_approx(asec(-1.5707963267949, 'degrees'), 129.54022374781, 'asec - degrees');
is_approx(asec(1.5707963267949, 'degrees'), 50.4597762521899, 'asec - degrees');

is_approx(asec(-1.5707963267949, 'gradians'), 143.933581942011, 'asec - gradians');
is_approx(asec(1.5707963267949, 'gradians'), 56.0664180579888, 'asec - gradians');

is_approx(asec(-1.5707963267949, 'radians'), 2.26090341816943, 'asec - radians');
is_approx(asec(1.5707963267949, 'radians'), 0.880689235420358, 'asec - radians');

is_approx(asec(-1.5707963267949, 1), 0.359833954855028, 'asec - revolutions');
is_approx(asec(1.5707963267949, 1), 0.140166045144972, 'asec - revolutions');


# cosh
my @coshes = ( 
    AngleAndResult.new(0, 1),
    AngleAndResult.new(45, (exp(pi / 4.0) + exp(-pi / 4.0)) / 2.0),
    AngleAndResult.new(90, (exp(pi / 2.0) + exp(-pi / 2.0)) / 2.0),
    AngleAndResult.new(180, (exp(pi) + exp(-pi)) / 2.0)
);

for @coshes -> $angle
{
    my $cosh = $angle.result;
    
    # cosh(Num)
	is_approx(cosh($angle.num("radians")), $cosh, 
	          "cosh(Num) - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(cosh($angle.num($base), %official_base{$base}), $cosh, 
	              "cosh(Num) - {$angle.num($base)} $base");
	}
	
	# MUST: Add all the other forms
}

is(cosh(Inf), Inf);
is(cosh(-Inf), Inf);


# sinh
my @sinhes = ( 
    AngleAndResult.new(0, 0.0),
    AngleAndResult.new(45, (exp(pi / 4.0) - exp(-pi / 4.0)) / 2.0),
    AngleAndResult.new(90, (exp(pi / 2.0) - exp(-pi / 2.0)) / 2.0),
    AngleAndResult.new(180, (exp(pi) - exp(-pi)) / 2.0)
);

for @sinhes -> $angle
{
    my $sinh = $angle.result;
    
    # sinh(Num)
	is_approx(sinh($angle.num("radians")), $sinh, 
	          "sinh(Num) - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(sinh($angle.num($base), %official_base{$base}), $sinh, 
	              "sinh(Num) - {$angle.num($base)} $base");
	}
	
	# MUST: Add all the other forms
}

# sech
for @coshes -> $angle
{
    my $sech = 1.0 / $angle.result;
    
    # sech(Num)
	is_approx(sech($angle.num("radians")), $sech, 
	          "sech(Num) - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(sech($angle.num($base), %official_base{$base}), $sech, 
	              "sech(Num) - {$angle.num($base)} $base");
	}
	
	# MUST: Add all the other forms
}


# tanh
my @tanhes = ( 
    AngleAndResult.new(0, 0.0),
    AngleAndResult.new(45, (exp(pi / 2.0) - 1.0) / (exp(pi / 2.0) + 1.0)),
    AngleAndResult.new(90, (exp(pi) - 1.0) / (exp(pi) + 1.0)),
    AngleAndResult.new(180, (exp(2.0 * pi) - 1.0) / (exp(2.0 * pi) + 1.0)),
);

for @tanhes -> $angle
{
    my $tanh = $angle.result;
    
    # tanh(Num)
	is_approx(tanh($angle.num("radians")), $tanh, 
	          "tanh(Num) - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(tanh($angle.num($base), %official_base{$base}), $tanh, 
	              "tanh(Num) - {$angle.num($base)} $base");
	}
	
	# MUST: Add all the other forms
}


# cosech
for @sinhes -> $angle
{
    next if $angle.result == 0.0;
    my $cosech = 1.0 / $angle.result;
    
    # cosech(Num)
	is_approx(cosech($angle.num("radians")), $cosech, 
	          "cosech(Num) - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(cosech($angle.num($base), %official_base{$base}), $cosech, 
	              "cosech(Num) - {$angle.num($base)} $base");
	}
	
	# MUST: Add all the other forms
}


# cotanh
for @tanhes -> $angle
{
    next if $angle.result == 0.0;
    my $cotanh = 1.0 / $angle.result;
    
    # cotanh(Num)
	is_approx(cotanh($angle.num("radians")), $cotanh, 
	          "cotanh(Num) - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(cotanh($angle.num($base), %official_base{$base}), $cotanh, 
	              "cotanh(Num) - {$angle.num($base)} $base");
	}
	
	# MUST: Add all the other forms
}


# acosh
is_approx(acosh(1.5707963267949), 1.02322747854755, 'acosh - default');
is_approx(acosh(3.14159265358979), 1.81152627246085, 'acosh - default');

is_approx(acosh(1.5707963267949, 'degrees'), 58.6266160025878, 'acosh - degrees');
is_approx(acosh(3.14159265358979, 'degrees'), 103.792809889073, 'acosh - degrees');

is_approx(acosh(1.5707963267949, 'gradians'), 65.1406844473198, 'acosh - gradians');
is_approx(acosh(3.14159265358979, 'gradians'), 115.325344321192, 'acosh - gradians');

is_approx(acosh(1.5707963267949, 'radians'), 1.02322747854755, 'acosh - radians');
is_approx(acosh(3.14159265358979, 'radians'), 1.81152627246085, 'acosh - radians');

is_approx(acosh(1.5707963267949, 1), 0.162851711118299, 'acosh - revolutions');
is_approx(acosh(3.14159265358979, 1), 0.28831336080298, 'acosh - revolutions');


# asinh
is_approx(asinh(1.5707963267949), 1.23340311751122, 'asinh - default');
is_approx(asinh(3.14159265358979), 1.86229574331085, 'asinh - default');

is_approx(asinh(1.5707963267949, 'degrees'), 70.6687930716712, 'asinh - degrees');
is_approx(asinh(3.14159265358979, 'degrees'), 106.70168629689, 'asinh - degrees');

is_approx(asinh(1.5707963267949, 'gradians'), 78.5208811907457, 'asinh - gradians');
is_approx(asinh(3.14159265358979, 'gradians'), 118.557429218767, 'asinh - gradians');

is_approx(asinh(1.5707963267949, 'radians'), 1.23340311751122, 'asinh - radians');
is_approx(asinh(3.14159265358979, 'radians'), 1.86229574331085, 'asinh - radians');

is_approx(asinh(1.5707963267949, 1), 0.196302202976864, 'asinh - revolutions');
is_approx(asinh(3.14159265358979, 1), 0.296393573046917, 'asinh - revolutions');


# asech
is_approx(asech(0.5), 1.31695789692482, 'asech - default');
is_approx(asech(1), 0, 'asech - default');

is_approx(asech(0.5, 'degrees'), 75.4561292902169, 'asech - degrees');
is_approx(asech(1, 'degrees'), 0, 'asech - degrees');

is_approx(asech(0.5, 'gradians'), 83.8401436557965, 'asech - gradians');
is_approx(asech(1, 'gradians'), 0, 'asech - gradians');

is_approx(asech(0.5, 'radians'), 1.31695789692482, 'asech - radians');
is_approx(asech(1, 'radians'), 0, 'asech - radians');

is_approx(asech(0.5, 1), 0.209600359139491, 'asech - revolutions');
is_approx(asech(1, 1), 0, 'asech - revolutions');


# atanh
is_approx(atanh(0), 0, 'atanh - default');
is_approx(atanh(0.5), 0.549306144334055, 'atanh - default');

is_approx(atanh(0, 'degrees'), 0, 'atanh - degrees');
is_approx(atanh(0.5, 'degrees'), 31.4729237309454, 'atanh - degrees');

is_approx(atanh(0, 'gradians'), 0, 'atanh - gradians');
is_approx(atanh(0.5, 'gradians'), 34.969915256606, 'atanh - gradians');

is_approx(atanh(0, 'radians'), 0, 'atanh - radians');
is_approx(atanh(0.5, 'radians'), 0.549306144334055, 'atanh - radians');

is_approx(atanh(0, 1), 0, 'atanh - revolutions');
is_approx(atanh(0.5, 1), 0.087424788141515, 'atanh - revolutions');


# cosec
for @sines -> $angle
{
    next if $angle.result == 0.0;
    my $cosec = 1.0 / $angle.result;
    
    # cosec(Num)
	is_approx(cosec($angle.num("radians")), $cosec, 
	          "cosec(Num) - {$angle.num('radians')} default");
	for %official_base.keys -> $base {
	    is_approx(cosec($angle.num($base), %official_base{$base}), $cosec, 
	              "cosec(Num) - {$angle.num($base)} $base");
	}
	
	# MUST: Add all the other forms
}


# acosec
is_approx(acosec(-1.5707963267949), -0.690107091374538, 'acosec - default');
is_approx(acosec(1.5707963267949), 0.690107091374538, 'acosec - default');

is_approx(acosec(-1.5707963267949, 'degrees'), -39.5402237478101, 'acosec - degrees');
is_approx(acosec(1.5707963267949, 'degrees'), 39.5402237478101, 'acosec - degrees');

is_approx(acosec(-1.5707963267949, 'gradians'), -43.9335819420112, 'acosec - gradians');
is_approx(acosec(1.5707963267949, 'gradians'), 43.9335819420112, 'acosec - gradians');

is_approx(acosec(-1.5707963267949, 'radians'), -0.690107091374538, 'acosec - radians');
is_approx(acosec(1.5707963267949, 'radians'), 0.690107091374538, 'acosec - radians');

is_approx(acosec(-1.5707963267949, 1), -0.109833954855028, 'acosec - revolutions');
is_approx(acosec(1.5707963267949, 1), 0.109833954855028, 'acosec - revolutions');


# acotan
is_approx(acotan(-1.5707963267949), -0.566911504941008, 'acotan - default');
is_approx(acotan(1.5707963267949), 0.566911504941008, 'acotan - default');

is_approx(acotan(-1.5707963267949, 'degrees'), -32.4816365905297, 'acotan - degrees');
is_approx(acotan(1.5707963267949, 'degrees'), 32.4816365905297, 'acotan - degrees');

is_approx(acotan(-1.5707963267949, 'gradians'), -36.0907073228108, 'acotan - gradians');
is_approx(acotan(1.5707963267949, 'gradians'), 36.0907073228108, 'acotan - gradians');

is_approx(acotan(-1.5707963267949, 'radians'), -0.566911504941008, 'acotan - radians');
is_approx(acotan(1.5707963267949, 'radians'), 0.566911504941008, 'acotan - radians');

is_approx(acotan(-1.5707963267949, 1), -0.0902267683070269, 'acotan - revolutions');
is_approx(acotan(1.5707963267949, 1), 0.0902267683070269, 'acotan - revolutions');


# acosech
is_approx(acosech(1.5707963267949), 0.599971479517856, 'acosech - default');
is_approx(acosech(3.14159265358979), 0.313165880450869, 'acosech - default');

is_approx(acosech(1.5707963267949, 'degrees'), 34.3758336045928, 'acosech - degrees');
is_approx(acosech(3.14159265358979, 'degrees'), 17.9430832373333, 'acosech - degrees');

is_approx(acosech(1.5707963267949, 'gradians'), 38.1953706717698, 'acosech - gradians');
is_approx(acosech(3.14159265358979, 'gradians'), 19.9367591525925, 'acosech - gradians');

is_approx(acosech(1.5707963267949, 'radians'), 0.599971479517856, 'acosech - radians');
is_approx(acosech(3.14159265358979, 'radians'), 0.313165880450869, 'acosech - radians');

is_approx(acosech(1.5707963267949, 1), 0.0954884266794246, 'acosech - revolutions');
is_approx(acosech(3.14159265358979, 1), 0.0498418978814813, 'acosech - revolutions');


# acotanh
is_approx(acotanh(-1.5707963267949), -0.752469267141925, 'acotanh - default');
is_approx(acotanh(1.5707963267949), 0.752469267141925, 'acotanh - default');

is_approx(acotanh(-1.5707963267949, 'degrees'), -43.1133132205344, 'acotanh - degrees');
is_approx(acotanh(1.5707963267949, 'degrees'), 43.1133132205344, 'acotanh - degrees');

is_approx(acotanh(-1.5707963267949, 'gradians'), -47.9036813561493, 'acotanh - gradians');
is_approx(acotanh(1.5707963267949, 'gradians'), 47.9036813561493, 'acotanh - gradians');

is_approx(acotanh(-1.5707963267949, 'radians'), -0.752469267141925, 'acotanh - radians');
is_approx(acotanh(1.5707963267949, 'radians'), 0.752469267141925, 'acotanh - radians');

is_approx(acotanh(-1.5707963267949, 1), -0.119759203390373, 'acotanh - revolutions');
is_approx(acotanh(1.5707963267949, 1), 0.119759203390373, 'acotanh - revolutions');


#?rakudo skip 'named args'
{

# -- atan
# The basic form of atan (one argument) returns a value in ]-pi, pi[.
# Quadrants I, III
is_approx(atan(:x(1))           / $PI * 180, 45);
is_approx(atan(:x(1/3*sqrt(3))) / $PI * 180, 30);
is_approx(atan(:x(sqrt(3)))     / $PI * 180, 60);

# Quadrants II, IV
is_approx(atan(:x(-1))           / $PI * 180, -45);
is_approx(atan(:x(-1/3*sqrt(3))) / $PI * 180, -30);
is_approx(atan(:x(-sqrt(3)))     / $PI * 180, -60);

# S29: C<atan2> computes the arctangent of $y/$x, and
# **takes the quadrant into account**. The second argument is
# assumed to be 1 if it is not present.
# Quadrant I
is_approx(atan2(:x(1), 1)           / $PI * 180, 45);
is_approx(atan2(:x(1))              / $PI * 180, 45);
is_approx(atan2(:x(1), sqrt(3))     / $PI * 180, 30);
is_approx(atan2(:x(1), 1/3*sqrt(3)) / $PI * 180, 60);

# Quadrant II
is_approx(atan2(:x(1), -1)           / $PI * 180, 135);
is_approx(atan2(:x(1), -1/3*sqrt(3)) / $PI * 180, 120);
is_approx(atan2(:x(1), -sqrt(3))     / $PI * 180, 150);

# Quadrant III
is_approx(atan2(x => -1, -1)           / $PI * 180 + 360, 225);
is_approx(atan2(x => -1, -sqrt(3))     / $PI * 180 + 360, 210);
is_approx(atan2(x => -1, -1/3*sqrt(3)) / $PI * 180 + 360, 240);

# Quadrant IV
is_approx(atan2(x => -1, 1)           / $PI * 180 + 360, 315);
is_approx(atan2(x => -1)              / $PI * 180 + 360, 315);
is_approx(atan2(x => -1, sqrt(3))     / $PI * 180 + 360, 330);
is_approx(atan2(x => -1, 1/3*sqrt(3)) / $PI * 180 + 360, 300);

# -- sin, cos, tan
# sin
is_approx(sin(:x(0/4*$PI)), 0);
is_approx(sin(:x(1/4*$PI)), 1/2*sqrt(2));
is_approx(sin(:x(2/4*$PI)), 1);
is_approx(sin(:x(3/4*$PI)), 1/2*sqrt(2));
is_approx(sin(:x(4/4*$PI)), 0);
is_approx(sin(:x(5/4*$PI)), -1/2*sqrt(2));
is_approx(sin(:x(6/4*$PI)), -1);
is_approx(sin(:x(7/4*$PI)), -1/2*sqrt(2));
is_approx(sin(:x(8/4*$PI)), 0);

# cos
is_approx(cos(:x(0/4*$PI)), 1);
is_approx(cos(:x(1/4*$PI)), 1/2*sqrt(2));
is_approx(cos(:x(2/4*$PI)), 0);
is_approx(cos(:x(3/4*$PI)), -1/2*sqrt(2));
is_approx(cos(:x(4/4*$PI)), -1);
is_approx(cos(:x(5/4*$PI)), -1/2*sqrt(2));
is_approx(cos(:x(6/4*$PI)), 0);
is_approx(cos(:x(7/4*$PI)), 1/2*sqrt(2));
is_approx(cos(:x(8/4*$PI)), 1);

# tan
is_approx(tan(:x(0/4*$PI)),  0);
is_approx(tan(:x(1/4*$PI)),  1);
is_approx(tan(:x(3/4*$PI)), -1);
is_approx(tan(:x(4/4*$PI)),  0);
is_approx(tan(:x(5/4*$PI)),  1);
is_approx(tan(:x(7/4*$PI)), -1);
is_approx(tan(:x(8/4*$PI)),  0);

# sec
is_approx(sec(:x(0)),    1);
is_approx(sec(:x($PI)), -1);


# asin
is_approx(asin(:x(0)),            0);
#?pugs 2 todo 'feature'
is_approx(asin(:x(1/2*sqrt(2))),  1/4*$PI);
is_approx(asin(:x(1)),            2/4*$PI);

# acos
#?pugs 2 todo 'feature'
is_approx(acos(:x(0)),            2/4*$PI);
is_approx(acos(:x(1/2*sqrt(2))),  1/4*$PI);
is_approx(acos(:x(1)),            0/4*$PI);


# cosh
is_approx( cosh(:x(0)), 1);
is_approx( cosh(:x(1)), 0.5*(exp(1) + exp(-1)) );

# sinh
is_approx( sinh(:x(0)), 0);
is_approx( sinh(:x(1)), 0.5*(exp(1) - exp(-1)) );

# asinh
is_approx( asinh(:x(0)), 0 );

# acosh
is_approx( acosh(:x(1)), 0 );

# tanh
is_approx( tanh(:x(0)), 0);
is_approx( tanh(:x(1)), sinh(1)/cosh(1) );

# atanh
is_approx( atanh(:x(0)), 0 );

# sech
is_approx( sech(:x(0)), 1 );

# cosech
is_approx( cosech(:x(2)), 1/sinh(2) );

# cotanh
is_approx( cotanh(:x(1)), 1/tanh(1) );

# asech
is_approx( asech(:x(1)), 0 );
# -- atan
# The basic form of atan (one argument) returns a value in ]-pi, pi[.
# Quadrants I, III
is_approx(atan(:x(1))           / $PI * 180, 45);
is_approx(atan(:x(1/3*sqrt(3))) / $PI * 180, 30);
is_approx(atan(:x(sqrt(3)))     / $PI * 180, 60);

# Quadrants II, IV
is_approx(atan(:x(-1))           / $PI * 180, -45);
is_approx(atan(:x(-1/3*sqrt(3))) / $PI * 180, -30);
is_approx(atan(:x(-sqrt(3)))     / $PI * 180, -60);

# S29: C<atan2> computes the arctangent of $y/$x, and
# **takes the quadrant into account**. The second argument is
# assumed to be 1 if it is not present.
# Quadrant I
is_approx(atan2(x => 1, 1)           / $PI * 180, 45);
is_approx(atan2(:x(1))              / $PI * 180, 45);
is_approx(atan2(x => 1, sqrt(3))     / $PI * 180, 30);
is_approx(atan2(x => 1, 1/3*sqrt(3)) / $PI * 180, 60);

# Quadrant II
is_approx(atan2(x => 1, -1)           / $PI * 180, 135);
is_approx(atan2(x => 1, -1/3*sqrt(3)) / $PI * 180, 120);
is_approx(atan2(x => 1, -sqrt(3))     / $PI * 180, 150);

# Quadrant III
is_approx(atan2(x => -1, -1)           / $PI * 180 + 360, 225);
is_approx(atan2(x => -1, -sqrt(3))     / $PI * 180 + 360, 210);
is_approx(atan2(x => -1, -1/3*sqrt(3)) / $PI * 180 + 360, 240);

# Quadrant IV
is_approx(atan2(x => -1, 1)           / $PI * 180 + 360, 315);
is_approx(atan2(x => -1)              / $PI * 180 + 360, 315);
is_approx(atan2(x => -1, sqrt(3))     / $PI * 180 + 360, 330);
is_approx(atan2(x => -1, 1/3*sqrt(3)) / $PI * 180 + 360, 300);

# -- sin, cos, tan
# sin
is_approx(sin(:x(0/4*$PI)), 0);
is_approx(sin(:x(1/4*$PI)), 1/2*sqrt(2));
is_approx(sin(:x(2/4*$PI)), 1);
is_approx(sin(:x(3/4*$PI)), 1/2*sqrt(2));
is_approx(sin(:x(4/4*$PI)), 0);
is_approx(sin(:x(5/4*$PI)), -1/2*sqrt(2));
is_approx(sin(:x(6/4*$PI)), -1);
is_approx(sin(:x(7/4*$PI)), -1/2*sqrt(2));
is_approx(sin(:x(8/4*$PI)), 0);

# cos
is_approx(cos(:x(0/4*$PI)), 1);
is_approx(cos(:x(1/4*$PI)), 1/2*sqrt(2));
is_approx(cos(:x(2/4*$PI)), 0);
is_approx(cos(:x(3/4*$PI)), -1/2*sqrt(2));
is_approx(cos(:x(4/4*$PI)), -1);
is_approx(cos(:x(5/4*$PI)), -1/2*sqrt(2));
is_approx(cos(:x(6/4*$PI)), 0);
is_approx(cos(:x(7/4*$PI)), 1/2*sqrt(2));
is_approx(cos(:x(8/4*$PI)), 1);

# tan
is_approx(tan(:x(0/4*$PI)),  0);
is_approx(tan(:x(1/4*$PI)),  1);
is_approx(tan(:x(3/4*$PI)), -1);
is_approx(tan(:x(4/4*$PI)),  0);
is_approx(tan(:x(5/4*$PI)),  1);
is_approx(tan(:x(7/4*$PI)), -1);
is_approx(tan(:x(8/4*$PI)),  0);

# sec
is_approx(sec(:x(0)),    1);
is_approx(sec(:x($PI)), -1);


# asin
is_approx(asin(:x(0)),            0);
#?pugs 2 todo 'feature'
is_approx(asin(:x(1/2*sqrt(2))),  1/4*$PI);
is_approx(asin(:x(1)),            2/4*$PI);

# acos
#?pugs 2 todo 'feature'
is_approx(acos(:x(0)),            2/4*$PI);
is_approx(acos(:x(1/2*sqrt(2))),  1/4*$PI);
is_approx(acos(:x(1)),            0/4*$PI);


# cosh
is_approx( cosh(:x(0)), 1);
is_approx( cosh(:x(1)), 0.5*(exp(1) + exp(-1)) );

# sinh
is_approx( sinh(:x(0)), 0);
is_approx( sinh(:x(1)), 0.5*(exp(1) - exp(-1)) );

# asinh
is_approx( asinh(:x(0)), 0 );

# acosh
is_approx( acosh(:x(1)), 0 );

# tanh
is_approx( tanh(:x(0)), 0);
is_approx( tanh(:x(1)), sinh(1)/cosh(1) );

# atanh
is_approx( atanh(:x(0)), 0 );

# sech
is_approx( sech(:x(0)), 1 );

# cosech
is_approx( cosech(:x(2)), 1/sinh(2) );

# cotanh
is_approx( cotanh(:x(1)), 1/tanh(1) );

# asech
is_approx( asech(:x(1)), 0 );
}

done_testing;

# vim: ft=perl6