my class X::Cannot::Capture        { ... }
my class X::Numeric::DivideByZero  { ... }
my class X::Numeric::CannotConvert { ... }

my class Num does Real { # declared in BOOTSTRAP
    # class Num is Cool
    #     has num $!value is box_target;

    multi method WHICH(Num:D: --> ValueObjAt:D) {
        nqp::box_s(
          nqp::concat(
            nqp::if(
              nqp::eqaddr(self.WHAT,Num),
              'Num|',
              nqp::concat(nqp::unbox_s(self.^name), '|')
            ),
            nqp::unbox_n(self)
          ),
          ValueObjAt
        )
    }
    multi method Bool(Num:D:) { nqp::hllbool(nqp::isne_n(self,0e0)) }
    method Capture() { X::Cannot::Capture.new( :what(self) ).throw }
    method Num() { self }
    method Bridge(Num:) { self.defined ?? self !! self.Real::Bridge }
    method Range(Num:U:) { Range.new(-Inf,Inf) }

    method Int(Num:D:) {
        nqp::isnanorinf(nqp::unbox_n(self))
          ?? X::Numeric::CannotConvert.new(:source(self), :target(Int)).fail
          !! nqp::fromnum_I(nqp::unbox_n(self),Int)
    }

    method sign(Num:D:) {
        nqp::isnanorinf(self)
          ?? self == Inf ?? 1 !! self == -Inf ?? -1 !! NaN
          !! self  > 0   ?? 1 !! self <  0    ?? -1 !! 0
    }

    multi method new() { nqp::box_n(0e0, self) }
    multi method new($n) { nqp::box_n($n.Num, self) }

    multi method raku(Num:D:) {
        my str $res = self.Str;
        nqp::isnanorinf(nqp::unbox_n(self))
          || nqp::isge_i(nqp::index($res,'e'),0)
          || nqp::isge_i(nqp::index($res,'E'),0)
          ?? $res
          !! nqp::concat($res,'e0')
    }

    method Rat(Num:D: Real:D \epsilon = 1.0e-6, \RAT = Rat) {
        my num $num = self;

        return RAT.new(
          (nqp::iseq_n($num,$num) ?? nqp::iseq_n($num,Inf) ?? 1 !! -1 !! 0),
          0
        ) if nqp::isnanorinf($num);

        $num = nqp::neg_n($num) if (my int $signum = nqp::islt_n($num,0e0));
        my num $r = nqp::sub_n($num,nqp::floor_n($num));

        # basically have an Int
        if nqp::iseq_n($r,0e0) {
            RAT.new(nqp::fromnum_I(self,Int),1)
        }

        # find convergents of the continued fraction.
        else {
            my Int $a := 1;
            my Int $b := nqp::fromnum_I($num,Int);
            my Int $c := 0;
            my Int $d := 1;

            # bind some value to prevent Scalar container creation
            my Int $q      := 0;
            my Int $orig_b := 0;
            my Int $orig_d := 0;

            my num $modf_arg;
            my num $epsilon = epsilon.Num;

            nqp::while(
              nqp::isne_n($r,0e0)
                && nqp::isgt_n(
                     nqp::abs_n(nqp::sub_n($num,nqp::div_In($b,$d))),
                     $epsilon
                   ),
              nqp::stmts(
                ($modf_arg = nqp::div_n(1e0,$r)),
                ($q := nqp::fromnum_I($modf_arg,Int)),
                ($r  = nqp::sub_n($modf_arg,nqp::floor_n($modf_arg))),

                ($orig_b := $b),
                ($b := nqp::add_I(nqp::mul_I($q,$b,Int),$a,Int)),
                ($a := $orig_b),

                ($orig_d := $d),
                ($d := nqp::add_I(nqp::mul_I($q,$d,Int),$c,Int)),
                ($c := $orig_d)
              )
            );

            # Note that this result has less error than any Rational with a
            # smaller denominator but it is not (necessarily) the Rational
            # with the smallest denominator that has less than $epsilon error.
            # However, to find that Rational would take more processing.
            RAT.new($signum ?? nqp::neg_I($b,Int) !! $b, $d)
        }
    }
    method FatRat(Num:D: Real $epsilon = 1.0e-6) {
        self.Rat($epsilon, FatRat);
    }

    multi method atan2(Num:D: Num:D $x = 1e0) {
        nqp::p6box_n(nqp::atan2_n(nqp::unbox_n(self), nqp::unbox_n($x)));
    }

    multi method Str(Num:D:) {
        nqp::p6box_s(nqp::unbox_n(self));
    }

    method succ(Num:D:) { self + 1e0 }

    method pred(Num:D:) { self - 1e0 }

    method isNaN(Num:D: ) {
        self != self;
    }

    method abs(Num:D: ) {
        nqp::p6box_n(nqp::abs_n(nqp::unbox_n(self)));
    }

    multi method exp(Num:D: ) {
        nqp::p6box_n(nqp::exp_n(nqp::unbox_n(self)));
    }

    proto method log(|) {*}
    multi method log(Num:D: ) {
        nqp::p6box_n(nqp::log_n(nqp::unbox_n(self)));
    }
    multi method log(Num:D: Num \base) {
        self.log() / base.log();
    }

    multi method sqrt(Num:D: ) {
        nqp::p6box_n(nqp::sqrt_n(nqp::unbox_n(self)));
    }

    method rand(Num:D: ) {
        nqp::p6box_n(nqp::rand_n(nqp::unbox_n(self)));
    }

    method ceiling(Num:D: ) {
        nqp::isnanorinf(nqp::unbox_n(self))
            ?? self
            !! nqp::fromnum_I(nqp::ceil_n(nqp::unbox_n(self)), Int);
    }
    method floor(Num:D: ) {
        nqp::isnanorinf(nqp::unbox_n(self))
            ?? self
            !! nqp::fromnum_I(nqp::floor_n(nqp::unbox_n(self)), Int);
    }

    proto method sin(|) {*}
    multi method sin(Num:D: ) {
        nqp::p6box_n(nqp::sin_n(nqp::unbox_n(self)));
    }
    proto method asin(|) {*}
    multi method asin(Num:D: ) {
        nqp::p6box_n(nqp::asin_n(nqp::unbox_n(self)));
    }
    proto method cos(|) {*}
    multi method cos(Num:D: ) {
        nqp::p6box_n(nqp::cos_n(nqp::unbox_n(self)));
    }
    proto method acos(|) {*}
    multi method acos(Num:D: ) {
        nqp::p6box_n(nqp::acos_n(nqp::unbox_n(self)));
    }
    proto method tan(|) {*}
    multi method tan(Num:D: ) {
        nqp::p6box_n(nqp::tan_n(nqp::unbox_n(self)));
    }
    proto method atan(|) {*}
    multi method atan(Num:D: ) {
        nqp::p6box_n(nqp::atan_n(nqp::unbox_n(self)));
    }
    proto method sec(|) {*}
    multi method sec(Num:D: ) {
        nqp::p6box_n(nqp::div_n(1e0, nqp::cos_n(nqp::unbox_n(self))));
    }
    proto method asec(|) {*}
    multi method asec(Num:D: ) {
        nqp::p6box_n(nqp::acos_n(nqp::div_n(1e0, nqp::unbox_n(self))));
    }
    method cosec(Num:D:) {
        nqp::p6box_n(nqp::div_n(1e0, nqp::sin_n(nqp::unbox_n(self))));
    }
    method acosec(Num:D:) {
        nqp::p6box_n(nqp::asin_n(nqp::div_n(1e0, nqp::unbox_n(self))));
    }
    method cotan(Num:D:) {
        nqp::p6box_n(nqp::div_n(1e0, nqp::tan_n(nqp::unbox_n(self))));
    }
    method acotan(Num:D:) {
        nqp::p6box_n(nqp::atan_n(nqp::div_n(1e0, nqp::unbox_n(self))));
    }
    proto method sinh(|) {*}
    multi method sinh(Num:D: ) {
        nqp::p6box_n(nqp::sinh_n(nqp::unbox_n(self)));
    }
    proto method asinh(|) {*}
    multi method asinh(Num:D: ) {
        nqp::isnanorinf(self)
            ?? self
            !!
                self >= 0
                    ?? (self + (self * self + 1e0).sqrt).log
                    !! -(-1e0 * self).asinh
    }
    proto method cosh(|) {*}
    multi method cosh(Num:D: ) {
        nqp::p6box_n(nqp::cosh_n(nqp::unbox_n(self)));
    }
    proto method acosh(|) {*}
    multi method acosh(Num:D: ) {
        self < 1e0
            ?? NaN
            !! (self + (self * self - 1e0).sqrt).log;
    }
    proto method tanh(|) {*}
    multi method tanh(Num:D: ) {
        nqp::p6box_n(nqp::tanh_n(nqp::unbox_n(self)));
    }
    proto method atanh(|) {*}
    multi method atanh(1e0:) { ∞ }
    multi method atanh(Num:D: ) {
        ((1e0 + self) / (1e0 - self)).log / 2e0;
    }
    proto method sech(|) {*}
    multi method sech(Num:D: ) {
        nqp::p6box_n(nqp::div_n(1e0, nqp::cosh_n(nqp::unbox_n(self))));
    }
    proto method asech(|) {*}
    multi method asech(Num:D: ) {
        (1e0 / self).acosh;
    }
    proto method cosech(|) {*}
    multi method cosech(Num:D: ) {
        nqp::p6box_n(nqp::div_n(1e0, nqp::sinh_n(nqp::unbox_n(self))));
    }
    proto method acosech(|) {*}
    multi method acosech(Num:D: ) {
        (1e0 / self).asinh;
    }
    proto method cotanh(|) {*}
    multi method cotanh(Num:D: ) {
        nqp::p6box_n(nqp::div_n(1e0, nqp::tanh_n(nqp::unbox_n(self))));
    }
    proto method acotanh(|) {*}
    multi method acotanh(Num:D: ) {
        (1e0 / self).atanh;
    }
    method is-prime(--> Bool:D) {
        nqp::hllbool(
          nqp::if(
            nqp::isnanorinf(self),
            False,
            nqp::if(
              nqp::iseq_n(self,nqp::floor_n(self)),
              nqp::fromnum_I(self,Int).is-prime
            )
          )
        )
    }

    method narrow(Num:D:) {
        my $i := self.Int;
        $i.defined && $i.Num ≅ self
            ?? $i
            !! self
    }

    method UPGRADE-RAT(\nu, \de) is raw {
        nqp::p6box_n(nqp::div_In(nu,de))  # downgrade to float
    }
}

my constant tau = 6.28318_53071_79586_476e0;
my constant pi  = 3.14159_26535_89793_238e0;
my constant e   = 2.71828_18284_59045_235e0;

my constant π := pi;
my constant τ := tau;
#?if !jvm
my constant 𝑒 := e;
#?endif

multi sub prefix:<++>(Num:D $a is rw) {
    $a = nqp::p6box_n(nqp::add_n(nqp::unbox_n($a), 1e0))
}
multi sub prefix:<++>(Num:U $a is rw) {
    $a = 1e0;
}
multi sub prefix:<++>(num $a is rw --> num) {
    $a = nqp::add_n($a, 1e0)
}
multi sub prefix:<-->(Num:D $a is rw) {
    $a = nqp::p6box_n(nqp::sub_n(nqp::unbox_n($a), 1e0))
}
multi sub prefix:<-->(Num:U $a is rw) {
    $a = -1e0;
}
multi sub prefix:<-->(num $a is rw --> num) {
    $a = nqp::sub_n($a, 1e0)
}
multi sub postfix:<++>(Num:D $a is rw) {
    my $b = $a;
    $a = nqp::p6box_n(nqp::add_n(nqp::unbox_n($a), 1e0));
    $b
}
multi sub postfix:<++>(Num:U $a is rw --> 0e0) {
    $a = 1e0;
}
multi sub postfix:<++>(num $a is rw --> num) {
    my num $b = $a;
    $a = nqp::add_n($a, 1e0);
    $b
}
multi sub postfix:<-->(Num:D $a is rw) {
    my $b = $a;
    $a = nqp::p6box_n(nqp::sub_n(nqp::unbox_n($a), 1e0));
    $b
}
multi sub postfix:<-->(Num:U $a is rw --> 0e0) {
    $a = -1e0;
}
multi sub postfix:<-->(num $a is rw --> num) {
    my num $b = $a;
    $a = nqp::sub_n($a, 1e0);
    $b
}

multi sub prefix:<->(Num:D $a --> Num:D) { nqp::p6box_n(nqp::neg_n($a)) }
multi sub prefix:<->(num   $a --> num)   { nqp::neg_n($a)               }

multi sub abs(Num:D $a --> Num:D) { nqp::p6box_n(nqp::abs_n($a)) }
multi sub abs(num   $a --> num)   { nqp::abs_n($a)               }

multi sub infix:<+>(Num:D $a, Num:D $b) {
    nqp::p6box_n(nqp::add_n($a,$b))
}
multi sub infix:<+>(num $a, num $b --> num) {
    nqp::add_n($a, $b)
}

multi sub infix:<->(Num:D $a, Num:D $b) {
    nqp::p6box_n(nqp::sub_n($a,$b))
}
multi sub infix:<->(num $a, num $b --> num) {
    nqp::sub_n($a, $b)
}

multi sub infix:<*>(Num:D $a, Num:D $b) {
    nqp::p6box_n(nqp::mul_n($a,$b))
}
multi sub infix:<*>(num $a, num $b --> num) {
    nqp::mul_n($a, $b)
}

multi sub infix:</>(Num:D $a, Num:D $b) {
    $b
      ?? nqp::p6box_n(nqp::div_n($a,$b))
      !! X::Numeric::DivideByZero.new(:using</>, :numerator($a)).Failure
}
multi sub infix:</>(num $a, num $b --> num) {
    $b
      ?? nqp::div_n($a, $b)
      !! fail X::Numeric::DivideByZero.new(:using</>, :numerator($a))
}

multi sub infix:<%>(Num:D $a, Num:D $b) {
    $b
      ?? nqp::p6box_n(nqp::mod_n($a,$b))
      !! X::Numeric::DivideByZero.new(:using<%>, :numerator($a)).Failure
}
multi sub infix:<%>(num $a, num $b --> num) {
    $b
      ?? nqp::mod_n($a, $b)
      !! fail X::Numeric::DivideByZero.new(:using<%>, :numerator($a))
}

# (If we get 0 here, must be underflow, since floating overflow provides Inf.)
multi sub infix:<**>(Num:D $a, Num:D $b) {
    nqp::p6box_n(nqp::pow_n($a,$b))
      or $a == 0e0 || $b.abs == Inf
        ?? 0e0
        !! X::Numeric::Underflow.new.Failure
}
multi sub infix:<**>(num $a, num $b --> num) {
    nqp::pow_n($a, $b)
      or $a == 0e0 || $b.abs == Inf
        ?? 0e0
        !! fail X::Numeric::Underflow.new
}

# Here we sort NaN in with string "NaN"
multi sub infix:<cmp>(Num:D $a, Num:D $b) {
    (my $cmp := nqp::cmp_n($a,$b))
      ?? ORDER($cmp)
      !! $a === $b
        ?? Same # === cares about signed zeros, we don't, so:
        !! nqp::iseq_n($a,0e0) && nqp::iseq_n($b,0e0)
          ?? Same
          !! $a.Stringy cmp $b.Stringy;
}
multi sub infix:<cmp>(num $a, num $b) {
    (my $cmp := nqp::cmp_n($a, $b))
      ?? ORDER($cmp)
      !! $a === $b
        ?? Same # === cares about signed zeros, we don't, so:
        !! nqp::iseq_n($a, 0e0) && nqp::iseq_n($b, 0e0)
          ?? Same
          !! $a.Stringy cmp $b.Stringy;
}

# Here we treat NaN as undefined
multi sub infix:«<=>»(Num:D $a, Num:D $b) {
    ORDER(nqp::cmp_n($a,$b)) or $a == $b ?? Same !! Nil
}
multi sub infix:«<=>»(num $a, num $b) {
    ORDER(nqp::cmp_n($a, $b)) or $a == $b ?? Same !! Nil
}

multi sub infix:<===>(Num:D $a, Num:D $b) {
    nqp::hllbool(
      nqp::eqaddr($a.WHAT,$b.WHAT)
        && (( # Both are NaNs
                   nqp::not_i(nqp::isle_n($a,nqp::inf))
                && nqp::not_i(nqp::isle_n($b,nqp::inf))
            )
          || (nqp::iseq_n($a,$b)
               && ( # if we're dealing with zeros, ensure the signs match
                 nqp::isne_n($a,0e0)
                 || nqp::if( # 1/-0 = -Inf; 1/0 = +Inf
                     nqp::islt_n(nqp::div_n(1e0,$a),0e0), # a is -0, if true:
                     nqp::islt_n(nqp::div_n(1e0,$b),0e0), #  check b is -0 too
                     nqp::isgt_n(nqp::div_n(1e0,$b),0e0), #  check b is +0 too
                 )
               )
           ))
    )
}
multi sub infix:<===>(num $a, num $b --> Bool:D) {
    nqp::hllbool(
        nqp::eqaddr($a.WHAT,$b.WHAT)
        && (( # Both are NaNs
                   nqp::not_i(nqp::isle_n($a,nqp::inf))
                && nqp::not_i(nqp::isle_n($b,nqp::inf))
            )
          || (nqp::iseq_n($a,$b)
               && ( # if we're dealing with zeros, ensure the signs match
                 nqp::isne_n($a, 0e0)
                 || nqp::if( # 1/-0 = -Inf; 1/0 = +Inf
                     nqp::islt_n(nqp::div_n(1e0,$a),0e0), # a is -0, if true:
                     nqp::islt_n(nqp::div_n(1e0,$b),0e0), #   check b is -0 too
                     nqp::isgt_n(nqp::div_n(1e0,$b),0e0), #   check b is +0 too
                 )
              )
            ))
    )
}

multi sub infix:<≅>( Inf,  Inf) { Bool::True }
multi sub infix:<≅>(-Inf, -Inf) { Bool::True }

multi sub infix:<==>(Num:D $a, Num:D $b --> Bool:D) {
    nqp::hllbool(nqp::iseq_n($a,$b))
}
multi sub infix:<==>(num $a, num $b --> Bool:D) {
    nqp::hllbool(nqp::iseq_n($a,$b))
}

multi sub infix:<!=>(Num:D $a, Num:D $b --> Bool:D) {
    nqp::hllbool(nqp::isne_n($a,$b))
}
multi sub infix:<!=>(num $a, num $b --> Bool:D) {
    nqp::hllbool(nqp::isne_n($a,$b))
}

multi sub infix:«<»(Num:D $a, Num:D $b --> Bool:D) {
    nqp::hllbool(nqp::islt_n($a,$b))
}
multi sub infix:«<»(num $a, num $b --> Bool:D) {
    nqp::hllbool(nqp::islt_n($a,$b))
}

multi sub infix:«<=»(Num:D $a, Num:D $b --> Bool:D) {
    nqp::hllbool(nqp::isle_n($a,$b))
}
multi sub infix:«<=»(num $a, num $b --> Bool:D) {
    nqp::hllbool(nqp::isle_n($a,$b))
}

multi sub infix:«>»(Num:D $a, Num:D $b --> Bool:D) {
    nqp::hllbool(nqp::isgt_n($a,$b))
}
multi sub infix:«>»(num $a, num $b --> Bool:D) {
    nqp::hllbool(nqp::isgt_n($a,$b))
}

multi sub infix:«>=»(Num:D $a, Num:D $b --> Bool:D) {
    nqp::hllbool(nqp::isge_n($a,$b))
}
multi sub infix:«>=»(num $a, num $b --> Bool:D) {
    nqp::hllbool(nqp::isge_n($a,$b))
}

proto sub rand(*%) {*}
multi sub rand(--> Num:D) { nqp::p6box_n(nqp::rand_n(1e0)) }

proto sub srand($, *%) {*}
multi sub srand(Int:D $seed --> Int:D) { nqp::p6box_i(nqp::srand($seed)) }

multi sub atan2(Num:D $a, Num:D $b = 1e0) {
    nqp::p6box_n(nqp::atan2_n($a,$b))
}

multi sub cosec(Num:D $x) {
    nqp::p6box_n(nqp::div_n(1e0,nqp::sin_n($x)))
}
multi sub acosec(Num:D $x) {
    nqp::p6box_n(nqp::asin_n(nqp::div_n(1e0,$x)))
}

multi sub log( num $x --> num) { nqp::log_n($x)  }
multi sub sin( num $x --> num) { nqp::sin_n($x)  }
multi sub asin(num $x --> num) { nqp::asin_n($x) }
multi sub cos( num $x --> num) { nqp::cos_n($x)  }
multi sub acos(num $x --> num) { nqp::acos_n($x) }
multi sub tan( num $x --> num) { nqp::tan_n($x)  }
multi sub atan(num $x --> num) { nqp::atan_n($x) }

multi sub sec(    num $x --> num) { nqp::div_n(1e0,nqp::cos_n($x))   }
multi sub asec(   num $x --> num) { nqp::acos_n(nqp::div_n(1e0,$x))  }
multi sub cotan(  num $x --> num) { nqp::div_n(1e0, nqp::tan_n($x))  }
multi sub acotan( num $x --> num) { nqp::atan_n(nqp::div_n(1e0, $x)) }
multi sub sinh(   num $x --> num) { nqp::sinh_n($x) }
multi sub cosh(   num $x --> num) { nqp::cosh_n($x) }
multi sub tanh(   num $x --> num) { nqp::tanh_n($x) }
multi sub sech(   num $x --> num) { 1e0 / cosh($x)  }
multi sub asech(  num $x --> num) { acosh(1e0 / $x) }
multi sub cosech( num $x --> num) { 1e0 / sinh($x)  }
multi sub acosech(num $x --> num) { asinh(1e0 / $x) }
multi sub cotanh( num $x --> num) { 1e0 / tanh($x)  }
multi sub acotanh(num $x --> num) { atanh(1e0 / $x) }

multi sub asinh(num $x --> num) {
    # ln(x + √(x²+1))
    nqp::isnanorinf($x)
      ?? $x
      !! $x >= 0
        ?? nqp::log_n(
             nqp::add_n($x,nqp::pow_n(nqp::add_n(nqp::mul_n($x,$x),1e0),.5e0))
           )
        !! -asinh(-$x)
}
multi sub acosh(num $x --> num) {
    # ln(x + √(x²-1))
    $x < 1e0
      ?? NaN
      !! nqp::log_n(
           nqp::add_n($x,nqp::pow_n(nqp::sub_n(nqp::mul_n($x,$x),1e0),.5e0))
         )
}
multi sub atanh(num $x --> num) {
    $x == 1e0 ?? Inf !! log((1e0 + $x) / (1e0 - $x)) / 2e0;
}

multi sub floor(  num $a --> num) { nqp::floor_n($a) }
multi sub ceiling(num $a --> num) { nqp::ceil_n($a)  }
multi sub sqrt(   num $a --> num) { nqp::sqrt_n($a)  }

# vim: expandtab shiftwidth=4
