# -*- tcl -*-
# bignum.test --
#    Test cases for the ::math::bignum package
#

# -------------------------------------------------------------------------

source [file join \
	[file dirname [file dirname [file join [pwd] [info script]]]] \
	devtools testutilities.tcl]

testsNeedTcl     8.5
testsNeedTcltest 2.1

support {
    useLocal math.tcl math
}
testing {
    useLocal bignum.tcl math::bignum
}

# -------------------------------------------------------------------------

proc matchBignums { expected actual } {
   set match 1
   foreach a $actual e $expected {
      if { $a != $b } {
         set match 0
         break
      }
   }
   return $match
}

#
# Note:
# Some tests use the internal representation directly.
# The variables atombits is assumed to be 16
#
if { $::math::bignum::atombits != 16 } {
   puts "Prerequisite: atombits = 16"
   #
   # The maximum value for the atoms is 2**16-1 = 65535
   #
}

# -------------------------------------------------------------------------

#
# Tests: fromstr/tostr (use the internal representation directly)
#
test "Fromstr-1.0" "Convert string representing small number (1)" -body {
   ::math::bignum::fromstr 1
} -result {bignum 0 1}

test "Fromstr-1.1" "Convert string representing small number (2)" -body {
   ::math::bignum::fromstr 257
} -result {bignum 0 257}

test "Fromstr-1.2" "Convert string representing big number (1)" -body {
   ::math::bignum::fromstr "[expr {256*256*256}]"
} -result {bignum 0 0 256}

test "Fromstr-1.3" "Convert string representing big number (2)" -body {
   ::math::bignum::fromstr "[expr {256*256*256+1}]"
} -result {bignum 0 1 256}

test "Fromstr-1.4" "Convert string representing negative number" -body {
   ::math::bignum::fromstr "[expr {-256*256*256-1}]"
} -result {bignum 1 1 256}

test "Fromstr-1.5" "Convert string representing binary number (1)" -body {
   ::math::bignum::fromstr "10000000000000000000000000000000" 2
} -result {bignum 0 0 32768}

test "Fromstr-1.6" "Convert string representing binary number (2)" -body {
   ::math::bignum::fromstr "10000000000000000000000000000001" 2
} -result {bignum 0 1 32768}

test "Fromstr-1.7" "Convert string representing hex number (1)" -body {
   ::math::bignum::fromstr "ffffffff" 16
} -result {bignum 0 65535 65535}

test "Fromstr-1.8" "Convert string representing hex number (2)" -body {
   ::math::bignum::fromstr "-ffffffff" 16
} -result {bignum 1 65535 65535}

test "Fromstr-1.9" "Convert string representing 2*16+1" -body {
   ::math::bignum::fromstr "65537"
} -result {bignum 0 1 1}

test "Fromstr-1.10" "Convert string representing 2*16" -body {
   ::math::bignum::fromstr "65536"
} -result {bignum 0 0 1}


test "Tostr-2.0" "Convert small number (1)" -body {
   ::math::bignum::tostr {bignum 0 1}
} -result 1

test "Tostr-2.1" "Convert small number (2)" -body {
   ::math::bignum::tostr {bignum 0 257}
} -result 257

test "Tostr-2.2" "Convert big number (1)" -body {
   ::math::bignum::tostr  {bignum 0 0 256}
} -result "[expr {256*256*256}]"

test "Tostr-2.3" "Convert big number (2)" -body {
   ::math::bignum::tostr  {bignum 0 1 256}
} -result "[expr {256*256*256+1}]"

test "Tostr-2.4" "Convert negative number" -body {
   ::math::bignum::tostr  {bignum 1 1 256}
} -result "[expr {-256*256*256-1}]"

test "Tostr-2.5" "Convert binary number (1)" -body {
   ::math::bignum::tostr {bignum 0 0 32768} 2
} -result  "10000000000000000000000000000000"

test "Tostr-2.6" "Convert binary number (2)" -body {
   ::math::bignum::tostr  {bignum 0 1 32768} 2
} -result  "10000000000000000000000000000001"

test "Tostr-2.7" "Convert hex number (1)" -body {
   ::math::bignum::tostr  {bignum 0 65535 65535} 16
} -result  "ffffffff"

test "Tostr-2.8" "Convert hex number (2)" -body {
   ::math::bignum::tostr  {bignum 1 65535 65535} 16
} -result "-ffffffff"

test "Tostr-2.9" "Convert very big number" -body {
   ::math::bignum::tostr [::math::bignum::fromstr "10000000000000000000"]
} -result "10000000000000000000"

test "Tostr-2.10" "Convert to ternary number" -body {
   ::math::bignum::tostr  {bignum 0 9} 3
} -result "100"

#
# Arithmetic operations
#
test "Plus-3.0" "Add two smallish numbers" -body {
   set a [::math::bignum::fromstr "100000"]
   set b [::math::bignum::fromstr "100001"]

   set c [::math::bignum::add $a $b]

   ::math::bignum::tostr $c
} -result "200001"

test "Plus-3.1" "Add two big numbers" -body {
   set a [::math::bignum::fromstr "100000000000000"]
   set b [::math::bignum::fromstr "100001000000001"]

   set c [::math::bignum::add $a $b]

   ::math::bignum::tostr $c
} -result "200001000000001"

test "Plus-3.2" "Add two very large numbers" -body {
   set a [::math::bignum::fromstr "1[string repeat 0 200]1"]
   set b [::math::bignum::fromstr "2[string repeat 0 200]2"]

   set c [::math::bignum::add $a $b]

   ::math::bignum::tostr $c
} -result "3[string repeat 0 200]3"

test "Plus-3.3" "Add zero to a large number" -body {
    set a [::math::bignum::fromstr "1[string repeat 0 200]1"]
    set b 0

    set c [::math::bignum::add $a $b]

    ::math::bignum::tostr $c
} -result "1[string repeat 0 200]1"

test "Plus-3.4" "Add one to a large number" -body {
    set a [::math::bignum::fromstr "1[string repeat 9 200]"]
    set b 1

    set c [::math::bignum::add $a $b]

    ::math::bignum::tostr $c
} -result "2[string repeat 0 200]"


test "Minus-3.2" "Subtract two smallish numbers" -body {
   set a [::math::bignum::fromstr "100000"]
   set b [::math::bignum::fromstr "100001"]

   set c [::math::bignum::sub $a $b]

   ::math::bignum::tostr $c
} -result "-1"

test "Minus-3.3" "Subtract two big numbers" -body {
   set a [::math::bignum::fromstr "100000000000000"]
   set b [::math::bignum::fromstr "100001000000001"]

   set c [::math::bignum::sub $a $b]

   ::math::bignum::tostr $c
} -result "-1000000001"

test "Minus-3.4" "Subtract one from a big number" -body {
    set a [::math::bignum::fromstr "1[string repeat 0 50]"]
    set b 1

    set c [::math::bignum::sub $a $b]

    ::math::bignum::tostr $c
} -result [string repeat 9 50]

test "Compare-4.0" "Compare a set of two numbers" -body {
   set okay 1
   foreach {astring bstring op} {
                           1                      -1 gt
                           1                      -1 ge
                           1                       1 ge
                           1                       1 eq
                          -1                       1 lt
                          -1                       1 le
                    10000000               -10000000 gt
                    10000000               -10000000 ge
                    10000000                10000000 eq
                   -10000000                10000000 lt
                   -10000000                10000000 le
                100000000000           -100000000000 gt
                100000000000           -100000000000 ge
                100000000000            100000000000 eq
               -100000000000            100000000000 lt
               -100000000000            100000000000 le
      1000000000000000000000 -1000000000000000000000 gt
      1000000000000000000000 -1000000000000000000000 ge
      1000000000000000000000  1000000000000000000000 eq
     -1000000000000000000000  1000000000000000000000 lt
     -1000000000000000000000  1000000000000000000000 le
     -1000000000000000000000  1000000000000000000000 ne
   } {
       set a [::math::bignum::fromstr $astring]
       set b [::math::bignum::fromstr $bstring]
       if { ! [::math::bignum::$op $a $b] } {
           set okay "False: $astring $op $bstring"
           break
       }
   }
   return $okay
} -result 1

test "Compare-4.1" "Compare a set of two numbers (inverse result)" -body {
   set okay 1
   foreach {astring bstring op} {
                          -1                       1 gt
                          -1                       1 ge
                           1                       1 ne
                           1                      -1 lt
                           1                      -1 le
                   -10000000                10000000 gt
                   -10000000                10000000 ge
                    10000000                10000000 ne
                    10000000               -10000000 lt
                    10000000               -10000000 le
               -100000000000            100000000000 gt
               -100000000000            100000000000 ge
                100000000000            100000000000 ne
                100000000000           -100000000000 lt
                100000000000           -100000000000 le
     -1000000000000000000000  1000000000000000000000 gt
     -1000000000000000000000  1000000000000000000000 ge
      1000000000000000000000  1000000000000000000000 ne
      1000000000000000000000 -1000000000000000000000 lt
      1000000000000000000000 -1000000000000000000000 le
      1000000000000000000000 -1000000000000000000000 eq
   } {
       set a [::math::bignum::fromstr $astring]
       set b [::math::bignum::fromstr $bstring]
       #
       # None should be true
       #
       if { [::math::bignum::$op $a $b] } {
           set okay "True: $astring $op $bstring - should be false"
           break
       }
   }
   return $okay
} -result 1

test "Compare-4.2" "Compare a set of numbers against 0 and 1" -body {
    set okay 1
    foreach {astring opzero opone} {
        -1                       lt lt
        1                        gt eq
        -10000000                lt lt
        10000000                 gt gt
        0                        eq lt
        2                        gt gt
    } {
        set a [::math::bignum::fromstr $astring]
        foreach b {0 1} op [list $opzero $opone] {
            #
            # None should be true
            #
            if  {! [::math::bignum::$op $a $b] } {
                set okay "False: $astring $op $b - should be true"
                break
            }
        }
    }
    return $okay
} -result 1


test "Mult-5.0" "Multiply two small numbers" -body {
   set a [::math::bignum::fromstr 10]
   set b [::math::bignum::fromstr 1000]

   set c [::math::bignum::mul $a $b]

   ::math::bignum::tostr $c
} -result "10000"

test "Mult-5.0a" "Multiply small numbers by 0" -body {
    set okay 1
    foreach a {1 0 -1 100000 -10000 100000000000 -100000000000} {
        set n [::math::bignum::fromstr $a]
        if {! [::math::bignum::iszero [::math::bignum::mul $n 0]]} {
            set okay "Multiplying $a by 0 does not give 0"
            return
        }
    }
    set okay
} -result 1

test "Mult-5.0b" "Multiply small numbers by 1" -body {
    set okay 1
    foreach a {1 0 -1 100000 -10000 100000000000 -100000000000} {
        set n [::math::bignum::fromstr $a]
        if {! [::math::bignum::eq [::math::bignum::mul $n 1] $n]} {
            set okay "Multiplying $a by 1 does not give $a"
            return
        }
    }
    set okay
} -result 1


test "Mult-5.1" "Multiply two small negative numbers" -body {
   set a [::math::bignum::fromstr -10]
   set b [::math::bignum::fromstr -1000]

   set c [::math::bignum::mul $a $b]

   ::math::bignum::tostr $c
} -result "10000"

test "Mult-5.2" "Multiply two very large numbers" -body {
   set a [::math::bignum::fromstr "1[string repeat 0 100]"]
   set b [::math::bignum::fromstr "2[string repeat 0 200]"]

   set c [::math::bignum::mul $a $b]

   ::math::bignum::tostr $c
} -result "2[string repeat 0 300]"

test "Mult-5.3" "Multiply two very large numbers of opposite sign" -body {
   set a [::math::bignum::fromstr "1[string repeat 0 100]"]
   set b [::math::bignum::fromstr "-2[string repeat 0 200]"]

   set c [::math::bignum::mul $a $b]

   ::math::bignum::tostr $c
} -result "-2[string repeat 0 300]"

test "Mult-5.4" "Katsabura multiplication with two very large numbers of opposite sign" -body {
   set a [::math::bignum::fromstr "1[string repeat 0 1000]"]
   set b [::math::bignum::fromstr "-2[string repeat 0 2000]"]

   set c [::math::bignum::mul $a $b]

   ::math::bignum::tostr $c
} -result "-2[string repeat 0 3000]"

# Div
test "Div-6.1" "Divide 0 by any number" -body {
    set okay 1
    foreach n {1 -1 2 -2 10 -10 1000000000 -100000000} {
        set a [::math::bignum::fromstr $n]
        if {! [::math::bignum::iszero [::math::bignum::div 0 $a]]} {
            set okay "Zero divided by $n does not give zero"
            break
        }
    }
    set okay
} -result 1


test "Div-6.2" "Divide small numbers by 1" -body {
    set okay 1
    foreach n {0 1 -1 2 -2 10 -10 1000000000 -100000000} {
        set a [::math::bignum::fromstr $n]
        if {! [::math::bignum::eq [::math::bignum::div $a 1] $a]} {
            set okay "$n divided by 1 does not give $n"
            break
        }
    }
    set okay
} -result 1

test "Div-6.3" "Divide big numbers by 2" -body {
    set okay 1
    set two [::math::bignum::fromstr 2]
    foreach p {2 5 10 50 100} {
        set n 1[string repeat 0 $p]
        set a [::math::bignum::fromstr $n]
        set q 5[string repeat 0 [expr {$p-1}]]
        if {! [string equal [::math::bignum::tostr [::math::bignum::div $a $two]] $q]} {
            set okay "$n divided by 2 does not give $q"
            break
        }
    }
    set okay
} -result 1

test "Pow-7.1" "Exponentiate large numbers" -body {
   set a [::math::bignum::fromstr "1[string repeat 0 10]"]
   set b [::math::bignum::fromstr 1]

   set okay 1
   foreach p {1 2 3 4 5 6 7 8 9 10} {
      set c [::math::bignum::mul $b $a]
      set d [::math::bignum::pow $a $p]

      if { [::math::bignum::ne $c $d] } {
         set okay "False: $a**$p != $c"
      }
   }
   return $okay
} -result 1

# Left and right shifts

set c 0
foreach {z n} {
   1             1
   2             1
   4             1
  -1             1
  -2             1
  -4             1
   1             2
   2             2
   4             2
  -1             2
  -2             2
  -4             2
   1000001       1
   2000001       1
   4000001       1
  -1000001       1
  -2000001       1
  -4000001       1
   10000000001   1
   20000000001   1
   40000000001   1
  -10000000001   1
  -20000000001   1
  -40000000001   1
   10000000001  11
   20000000001  11
   40000000001  11
  -10000000001  11
  -20000000001  11
  -40000000001  11
   10000000001  21
   20000000001  21
   40000000001  21
  -10000000001  21
  -20000000001  21
  -40000000001  21
} {
    incr c
    test "Lshift-8.$c" "Lshift large numbers" -body {
        set x [::math::bignum::lshift [::math::bignum::fromstr $z] $n]
        set y [expr {$z << $n}]
        ::math::bignum::cmp $x [::math::bignum::fromstr $y]
    } -result 0

    test "Rshift-8.$c" "Rshift large numbers" -body {
        set x [::math::bignum::rshift [::math::bignum::fromstr $z] $n]
        set y [expr {$z >> $n}]
        ::math::bignum::cmp $x [::math::bignum::fromstr $y]
    } -result 0
}

# Bit operations (And, Or, Xor)

foreach {n a b zand zor zxor} {
    0  0 0  0 0 0
    1  1 2  0 3 3
    2  1 3  1 3 2
    3  2 3  2 3 1
} {
    set a    [::math::bignum::fromstr $a]
    set b    [::math::bignum::fromstr $b]
    set zand [::math::bignum::fromstr $zand]
    set zor  [::math::bignum::fromstr $zor]
    set zxor [::math::bignum::fromstr $zxor]

    test "Bitand-8.$n" "BitAnd large numbers" -body {
	::math::bignum::bitand $a $b
    } -result $zand

    test "Bitor-9.$n" "BitOr large numbers" -body {
	::math::bignum::bitor $a $b
    } -result $zor

    test "Bitxor-10.$n" "BitXor large numbers" -body {
	::math::bignum::bitxor $a $b
    } -result $zxor
}

test "Mod-11.1" "Modulo and remainder for small numbers" -body {
    set okay 1
    foreach {n d m r} {
         100 -3 -2  1
        -100 -3 -1 -1
        -100  3  2 -1
         100  3  1  1
    }  {
        set a [::math::bignum::fromstr $n]
        set b [::math::bignum::fromstr $d]
        set modulo [::math::bignum::tostr [::math::bignum::mod $a $b]]
        set remainder [::math::bignum::tostr [::math::bignum::rem $a $b]]
        if {! [string equal $modulo $m]} {
            set okay "$n modulo $d does not give $m"
            break
        }
        if {! [string equal $remainder $r]} {
            set okay "the remainder of $n/$d is not given as $r"
            break
        }
    }
    return $okay
} -result 1


# Bit operations (Test bit)

test testbit-1.0 {test with bit in range of used bits} -setup {
    set z [::math::bignum::fromstr 3220]
    ::math::bignum::setbit z 24
} -body {
    ::math::bignum::testbit $z 23
} -cleanup {
    unset z
} -result 0

test testbit-1.1 {test with bit beyond range of used bits} -setup {
    set z [::math::bignum::fromstr 3220]
} -body {
    ::math::bignum::testbit $z 23
} -cleanup {
    unset z
} -result 0

test testbit-1.2 {test with bit in range of used bits} -setup {
    set z [::math::bignum::fromstr 3220]
    ::math::bignum::setbit z 24
} -body {
    ::math::bignum::testbit $z 24
} -cleanup {
    unset z
} -result 1

# -------------------------------------------------------------------------

#
# TODO: all the other operations and functions
#

# -------------------------------------------------------------------------

# End of test cases
testsuiteCleanup
