### ----------------------------------------------------
### -- Arithmetic::PaperAndPencil
### -- Licenses: Artistic-2.0
### -- Authors: Jean Forget
### -- File: xt/03-number.rakutest
### ----------------------------------------------------
# -*- encoding: utf-8; indent-tabs-mode: nil -*-

use Test;
use Arithmetic::PaperAndPencil::Number;

my @test-add =  (
         <10    6        9       15>
       , <13    6        9       12>
       , <30  004      007        B>
       , <16    4   2FFFFE   300002>
       , <16    4   FFFFFE  1000002>
       , <16  124        E      132>
       );

my @test-mult =  (
         <10    6    9  54>
       , <13    6    9  42> # See "The Restaurant at the end of the Universe", chapter 33, by Douglas Adams
       , <30  004  007  S>
       );

my @test-sub = (
         <10 1 54 61 7>
       , <10 8 54 58 4>
       , <16 1 54 61 D>
       , <16 8 54 58 4>
       );

my @test-cmp = (
         <10 2 12    88>
       , <10 5 12 99988>
       , <16 2 12    EE>
       , <16 5 12 FFFEE>
       , <10 15 8867700 999999991132300>
       , <10 15 999999991132300 8867700>
       );

my @test-div = (
         <10 18 1 18>
       , <10 18 2  9>
       , <16 C0 A 13>
       , <10 35 8  4>
       , <10 24 2 12>
       , <10 20 2 10>
       );
my @test-odd = (
         <10    1 1>
       , <10    2 0>
       , <10  152 0>
       , <10  243 1>
       , < 2 1011 1>
       , < 2 1010 0>
       , <11 1010 0>
       , <11 1510 1>
       );

plan 50 + @test-add.elems + @test-mult.elems + 2 × @test-sub.elems + @test-cmp.elems + @test-div.elems + @test-odd.elems;

# Check with the default radix
my Arithmetic::PaperAndPencil::Number $x .= new(value => '6');
my Arithmetic::PaperAndPencil::Number $y .= new(value => '9');
my Arithmetic::PaperAndPencil::Number $z  = max-unit(10);
is( $z.value, "9", "highest single-digit is 9");
$z  = $x ☈+ $y;
is( $z.value, "15", "6 plus 9 is 15");
$z = $y ☈- $x;
is( $z.value, "3", "9 minus 6 is 3");
$z = $x ☈- $x;
is( $z.value, "0", "6 minus 6 is 0");
$z = $x ☈× $y;
is( $z.value, "54", "6 times 9 is 54");
is( $z.gist , "54", "6 times 9 is 54 (using gist)");

is( $x.carry.value, "0", "carry of single-digit '6' is '0'");
is( $x.unit .value, "6", "unit of single-digit '6' is unaltered '6'");
is( $z.carry.value, "5", "carry of '54' is '5'");
is( $z.unit .value, "4", "unit of '54' is '4'");
is( $z.carry(2).value,  "0", "2-digit extended carry of '54' is '0'");
is( $z.unit( 2).value, "54", "2-digit extended unit of '54' is '54'");
is( $z.carry(3).value,  "0", "3-digit extended carry of '54' is '0'");
is( $z.unit( 3).value, "54", "3-digit extended unit of '54' is '54'");

is( $x.square-root.value, "2", "square root of 6 is 2");
is( $y.square-root.value, "3", "square root of 9 is 3");
is( $z.square-root.value, "7", "square root of 54 is 7");

is($x ☈<=> $y, Order::Less,  "6 numerically less than 9");
is($x ☈<=> $x, Order::Same,  "6 numerically same as 6");
is($z ☈<=> $x, Order::More, "54 numerically greater than 6");
is($x ☈leg $y, Order::Less,  "6 alphabetically less than 9");
is($z ☈leg $x, Order::Less, "54 alphabetically less than 6");
is($z ☈leg $z, Order::Same, "54 alphabetically same as 54");

is($x ☈<  $x, False,  "6 numerically less than 6? No");
is($x ☈<  $y, True ,  "6 numerically less than 9? Yes");
is($x ☈<  $z, True ,  "6 numerically less than 54? Yes");
is($y ☈<  $x, False,  "9 numerically less than 6? No");
is($y ☈<  $y, False,  "9 numerically less than 9? No");
is($y ☈<  $z, True ,  "9 numerically less than 54? Yes");
is($z ☈<  $x, False, "54 numerically less than 6? No");
is($z ☈<  $y, False, "54 numerically less than 9? No");
is($z ☈<  $z, False, "54 numerically less than 54? No");
is($x ☈lt $y, True ,  "6 alphabetically less than 9? Yes");
is($x ☈lt $z, False,  "6 alphabetically less than 54? No");
is($y ☈lt $x, False,  "9 alphabetically less than 6? No");
is($y ☈lt $z, False,  "9 alphabetically less than 54? No");
is($z ☈lt $x, True , "54 alphabetically less than 6? Yes");
is($z ☈lt $y, True , "54 alphabetically less than 9? Yes");
is($x ☈≤  $x, True ,  "6 numerically less than or equal to 6? Yes");
is($x ☈≤  $y, True ,  "6 numerically less than or equal to 9? Yes");
is($x ☈≤  $z, True ,  "6 numerically less than or equal to 54? Yes");
is($y ☈≤  $x, False,  "9 numerically less than or equal to 6? No");
is($y ☈≤  $y, True ,  "9 numerically less than or equal to 9? Yes");
is($y ☈≤  $z, True ,  "9 numerically less than or equal to 54? Yes");
is($z ☈≤  $x, False, "54 numerically less than or equal to 6? No");
is($z ☈≤  $y, False, "54 numerically less than or equal to 9? No");
is($z ☈≤  $z, True , "54 numerically less than or equal to 54? Yes");

$z .= new(value => '000');
is($z      .value, '0', "'000' is the same as '0'");
is($z.carry.value, "0", "carry of '000' is '0'");
is($z.unit .value, "0", "unit of '000' is '0'");

# Check with explicit radix
for @test-add -> $datum {
  check-add($datum[0].Int, $datum[1], $datum[2], $datum[3]);
}

for @test-mult -> $datum {
  check-mult($datum[0].Int, $datum[1], $datum[2], $datum[3]);
}

for @test-sub -> $datum {
  check-sub($datum[0].Int, $datum[1], $datum[2], $datum[3], $datum[4]);
}

for @test-cmp -> $datum {
  check-cmp($datum[0].Int, $datum[1], $datum[2], $datum[3]);
}

for @test-div -> $datum {
  check-div($datum[0].Int, $datum[1], $datum[2], $datum[3]);
}

for @test-odd -> $datum {
  check-odd($datum[0].Int, $datum[1], $datum[2].Bool);
}
done-testing;

sub check-add($radix, $x, $y, $sum) {
  my Arithmetic::PaperAndPencil::Number $xx .= new(value => $x, radix => $radix);
  my Arithmetic::PaperAndPencil::Number $yy .= new(value => $y, radix => $radix);
  my Arithmetic::PaperAndPencil::Number $zz  = $xx ☈+ $yy;
  is($zz.value, $sum, "$x plus $y is $sum (radix $radix)");
}

sub check-mult($radix, $x, $y, $pdt) {
  my Arithmetic::PaperAndPencil::Number $xx .= new(value => $x, radix => $radix);
  my Arithmetic::PaperAndPencil::Number $yy .= new(value => $y, radix => $radix);
  my Arithmetic::PaperAndPencil::Number $zz  = $xx ☈× $yy;
  is( $zz.value, $pdt, "$x times $y is $pdt (radix $radix)");
}

sub check-sub($radix, $x, $y, $x1, $s) {
  my Arithmetic::PaperAndPencil::Number $xx .= new(value => $x, radix => $radix);
  my Arithmetic::PaperAndPencil::Number $yy .= new(value => $y, radix => $radix);
  my Arithmetic::PaperAndPencil::Number $x-adj;
  my Arithmetic::PaperAndPencil::Number $rem;
  ($x-adj, $rem) = adjust-sub($xx, $yy);
  is( $x-adj.value, $x1, "$x - $y : adjusted to $x1 - $y (radix $radix)");
  is( $rem.value,   $s , "$x1 - $y = $s (radix $radix)");
}

sub check-cmp($radix, $len, $orig, $dest) {
  my Arithmetic::PaperAndPencil::Number $x .= new(value => $orig, radix => $radix);
  my Arithmetic::PaperAndPencil::Number $y  = $x.complement($len);
  is( $y.value, $dest, "{$radix}-complement of {$x.value} is $dest");

}

sub check-div(Int $radix, Str $dividend, Str $divisor, Str $quotient) {
  my Arithmetic::PaperAndPencil::Number $x .= new(radix => $radix, value => $dividend);
  my Arithmetic::PaperAndPencil::Number $y .= new(radix => $radix, value => $divisor);
  my Arithmetic::PaperAndPencil::Number $z = $x ☈÷ $y;
  is($z.value, $quotient, "$dividend ÷ $divisor = $quotient (radix $radix)");
}

sub check-odd(Int $radix, Str $value, Bool $result) {
  my Arithmetic::PaperAndPencil::Number $x .= new(radix => $radix, value => $value);
  my Str $comment = "even";
  if $result {
    $comment = "odd";
  }
  is($x.is-odd, $result, "$value (radix $radix) is $comment");
}


