Number/Binary.bruijn


# MIT License, Copyright (c) 2023 Marvin Borner
# binary implementation of T.Æ. Mogensen (see refs in README)
# TODO: Use abstract representation for logic instead of listifying

:import std/Combinator .
:import std/List .
:import std/Logic .

# TODO: remove ternary conversion shortcuts
# TODO: then move functions to reflect order of std/Ternary

:import std/Number/Ternary T

# bit indicating a one, compatible with std/Logic
b¹ true ⧗ Bit

# bit indicating a zero, compatible with std/Logic
b⁰ false ⧗ Bit

# returns true if a bit is set
b¹? i ⧗ Bit → Boolean

:test (b¹? b¹) (true)
:test (b¹? b⁰) (false)

# returns true if a bit is unset
b⁰? not! ⧗ Bit → Boolean

:test (b⁰? b⁰) (true)
:test (b⁰? b¹) (false)

# shifts a one into a binary number
↑¹‣ [[[[1 (3 2 1 0)]]]] ⧗ Binary → Binary

:test (↑¹[[[0 2]]]) ([[[1 (0 2)]]])

# shifts a zero into a binary number
↑⁰‣ [[[[0 (3 2 1 0)]]]] ⧗ Binary → Binary

:test (↑⁰(+1b)) ((+2b))

# shifts a specified bit into a binary number
up [[[[[4 1 0 (3 2 1 0)]]]]] ⧗ Bit → Binary → Binary

:test (up b¹ [[[0 2]]]) ([[[1 (0 2)]]])
:test (up b⁰ (+1b)) ((+2b))

# returns true if a binary number is zero
zero? [0 true [false] i] ⧗ Binary → Boolean

=?‣ zero?

:test (=?(+0b)) (true)
:test (=?[[[0 (0 2)]]]) (true)
:test (=?(+1b)) (false)

# extracts least significant bit from a binary number
lsb [0 b⁰ [b¹] [b⁰]] ⧗ Binary → Bit

:test (lsb (+0b)) (b⁰)
:test (lsb (+1b)) (b¹)
:test (lsb (+42b)) (b⁰)

# returns true if the number is even (remainder mod 2 == 0)
even? ¬‣ ∘ lsb ⧗ Binary → Boolean

=²?‣ even?

:test (even? (+0b)) (true)
:test (even? (+1b)) (false)
:test (even? (+41b)) (false)
:test (even? (+42b)) (true)

# converts the normal binary representation into abstract
abstract! [0 z a¹ a⁰] ⧗ Binary → AbstractBinary
	z (+0b)
	a¹ [[[[1 3]]]]
	a⁰ [[[[0 3]]]]

→^‣ abstract!

:test (→^(+2b)) ([[[0 [[[1 [[[2]]]]]]]]])

# converts the abstracted binary representation back to normal
normal! ω [[0 z a¹ a⁰]] ⧗ AbstractBinary → Binary
	z (+0b)
	a¹ [↑¹([3 3 0] 0)]
	a⁰ [↑⁰([3 3 0] 0)]

→_‣ normal!

:test (→_[[[0 [[[1 [[[2]]]]]]]]]) ((+2b))

# returns true if two binary numbers are equal
# → ignores leading 0s!
# also: ⋀?‣ ∘∘ (ψ* zip-with xnor? list!)
eq? [[abs 1 →^0]] ⧗ Binary → Binary → Boolean
	abs [0 z a¹ a⁰]
		z [=?(→_0)]
		a¹ [[0 false [2 0] [false]]]
		a⁰ [[0 (1 0) [false] [2 0]]]

…=?… eq?

:test ((+0b) =? (+0b)) (true)
:test ([[[1 (0 2)]]] =? [[[1 (0 2)]]]) (true)
:test ([[[1 2]]] =? (+2b)) (false)

# rshifts least significant bit of a binary number
div² [~(0 z a¹ a⁰)] ⧗ Binary → Binary
	z (+0b) : (+0b)
	a¹ &[[↑¹1 : 1]]
	a⁰ &[[↑⁰1 : 1]]

/²‣ div²

:test (/²(+6b) =? (+3b)) (true)
:test (/²(+5b) =? (+2b)) (true)

# subs 1 from a binary number (can introduce leading 0s)
dec [~(0 z a¹ a⁰)] ⧗ Binary → Binary
	z (+0b) : (+0b)
	a¹ &[[↑¹1 : ↑⁰1]]
	a⁰ &[[↑⁰1 : ↑¹0]]

--‣ dec

:test (--(+0b)) ((+0b))
:test (--(+1b)) ([[[0 2]]])
:test (--(+3b)) ((+2b))

# TODO: Remove (duplicate of std/Conversion because of dep loop)
binary→ternary [y [[[rec]]] [0] 0 (+0t)] ⧗ Binary → Ternary
	rec zero? 0 case-end case-rec
		case-rec even? 0 (2 (1 ∘ (T.mul (+2t))) (div² 0)) (2 (1 ∘ T.inc) (dec 0))
		case-end 1

# returns eq, gt, lt depending on comparison of two numbers
# TODO: remove ternary conversion
compare-case [[[(T.compare-case 2 1 0) ⋔ binary→ternary]]] ⧗ a → b → c → Binary → Binary → d

# returns true if number is greater than other number
# TODO: remove ternary conversion
gt? T.gt? ⋔ binary→ternary ⧗ Binary → Binary → Boolean

…>?… gt?

:test ((+1b) >? (+2b)) (false)
:test ((+2b) >? (+2b)) (false)
:test ((+3b) >? (+2b)) (true)

# ============================================================================ #
# most relevant functions are defined - we can now derive from Generic/Number! #
# ============================================================================ #

:input std/Generic/Number

# converts a binary number to a list of bits
list! [0 z a¹ a⁰] ⧗ Binary → (List Bit)
	z empty
	a¹ [b¹ : 0]
	a⁰ [b⁰ : 0]

:test (list! (+0b)) (empty)
:test (list! (+6b)) (b⁰ : (b¹ : {}b¹))

# converts a list of bits to a binary number
binary! foldr up (+0b) ⧗ (List Bit) → Binary

:test (binary! (list! (+0b))) ((+0b))
:test (binary! (list! (+42b))) ((+42b))

# strips leading 0s from a binary number
strip [^(0 z a¹ a⁰)] ⧗ Binary → Binary
	z (+0b) : true
	a¹ &[[↑¹1 : false]]
	a⁰ &[[(0 (+0b) ↑⁰1) : 0]]

%‣ strip

:test (%[[[0 2]]]) ((+0b))
:test (%[[[1 (0 (0 (0 (0 2))))]]]) ((+1b))
:test (%(+2b)) ((+2b))

# extracts most significant bit from a binary number
# not really useful for binary numbers, but part of interface
msb [=?0 b⁰ b¹] ⧗ Binary → Bit

:test (msb (+0b)) (b⁰)
:test (msb (+1b)) (b¹)
:test (msb (+42b)) (b¹)

# extracts nth bit from a binary number
nth …!!… ∘ list! ⧗ Binary → Number → Bit

# logical and on two binary numbers
and! binary! ∘∘ (ψ* zip-with …⋀?… list!) ⧗ Binary → Binary → Binary

…⋀!… and!

:test (and! (+1b) (+0b)) ((+0b))
:test (and! (+5b) (+4b)) ((+4b))
:test (and! (+10b) (+12b)) ((+8b))

# logical or on two binary numbers
# TODO: Fix for numbers with different length (→ zero padding?)
or! binary! ∘∘ (ψ* zip-with …⋁?… list!) ⧗ Binary → Binary → Binary

…⋁!… or!

:test (or! (+10b) (+12b)) ((+14b))

# :test (or! (+1b) (+0b)) ((+1b))
# :test (or! (+5b) (+3b)) ((+7b))

# adds 1 to a binary number (can introduce leading 0s)
inc [~(0 z a¹ a⁰)] ⧗ Binary → Binary
	z (+0b) : (+1b)
	a¹ &[[↑¹1 : ↑⁰0]]
	a⁰ &[[↑⁰1 : ↑¹1]]

++‣ inc

:test (++(+0b)) ((+1b))
:test (++(+2b)) ((+3b))

# flips the bits of a binary number (1's complement)
complement [[[[3 2 0 1]]]] ⧗ Binary → Binary

-*‣ complement

:test (-*(+0b) =? (+0b)) (true)
:test (-*(+1b) =? (+0b)) (true)
:test (-*(+42b)) ([[[1 (0 (1 (0 (1 (0 2)))))]]])

# inverts a binary number by complementing and incrementing (2's complement)
# don't forget to pad the number with 0s if needed
invert ++‣ ∘ -*‣ ⧗ Binary → Binary

-‣ invert

:test (-(+0b)) ((+1b))
:test (-(+1b)) ((+1b))

# pads a binary number with 0s until it's as long a another binary number
# TODO: this could be done without list magic (see Ternary)
pad [[binary! (pad-right ∀(list! %1) b⁰ (list! %0))]] ⧗ Binary → Binary → Binary

:test (pad (+5b) [[[1 2]]]) ([[[1 (0 (0 2))]]])

# adds two binary numbers (can introduce leading 0s)
# second argument gets abstracted (performance)
add [[abs 1 →^0]] ⧗ Binary → Binary → Binary
	abs [c (0 z a¹ a⁰)]
		c¹ [1 ↑⁰(3 0 b¹) ↑¹(3 0 b⁰)]
		a¹ [[[1 (c¹ 1) c¹' c¹]]]
			c¹' [up 1 (3 0 b¹)]
		a⁰ [[[1 (c⁰ 1) c¹ c⁰]]]
			c⁰ [up 1 (3 0 b⁰)]
		z [[0 ++(→_1) →_1]]
		c [[1 0 b⁰]]

…+… add

:test ((+0b) + (+0b) =? (+0b)) (true)
:test ((+0b) + (+3b) =? (+3b)) (true)
:test ((+1b) + (+2b) =? (+3b)) (true)
:test ((+42b) + (+1b) =? (+43b)) (true)
:test ((+1b) + (+42b) =? (+43b)) (true)

# subs two binary numbers (can introduce leading 0s)
# second argument gets abstracted (performance)
# TODO: fix sub*; implementation is completely wrong
sub* [[abs 1 →^0]] ⧗ Binary → Binary → Binary
	abs [c (0 z a¹ a⁰)]
		c¹ [1 ↑¹(3 0 b⁰) ↑¹(3 0 b⁰)]
		a¹ [[[1 (c¹ 1) c¹' c¹]]]
			c¹ [1 ↑⁰(3 0 b¹) ↑¹(3 0 b⁰)]
			c¹' [1 ↑¹(3 0 b⁰) ↑¹(3 0 b⁰)]
		a⁰ [[[1 (c⁰ 1) c⁰' c⁰]]]
			c⁰ [1 ↑¹(3 0 b⁰) ↑⁰(3 0 b⁰)]
			c⁰' [1 (3 0 b⁰) ↑¹(3 0 b¹)]
		z [[0 --(→_1) →_1]]
		c [[1 0 b⁰]]

# subs two binary numbers
# uses addition but with two's complement
# TODO: isn't very performant ⇒ replace with sub*
# TODO: gives fun results if b>a in a-b
sub [[(0 =? 1) (+0b) -((pad 1 0) + -(pad 0 1))]] ⧗ Binary → Binary → Binary

…-… sub

:test ((+42b) - (+12b) =? (+30b)) (true)
:test ((+3b) - (+0b) =? (+3b)) (true)
:test ((+3b) - (+2b) =? (+1b)) (true)

# muls two binary numbers (can introduce leading 0s)
mul [[1 z a¹ a⁰]] ⧗ Number → Number → Number
	z (+0b)
	a¹ [↑⁰0 + 1]
	a⁰ [↑⁰0]

…⋅… mul

:test ((+42b) ⋅ (+0b) =? (+0b)) (true)
:test ((+3b) ⋅ (+11b) =? (+33b)) (true)

# ceiled integer log2 by counting bits
# also counts leading 0s
log2* [0 (+0b) inc inc] ⧗ Binary → Binary

# ceiled integer log2 by counting bits
log2 log2* ∘ strip ⧗ Binary → Binary

:test ((log2 (+1b)) =? (+1b)) (true)
:test ((log2 (+2b)) =? (+2b)) (true)
:test ((log2 (+3b)) =? (+2b)) (true)
:test ((log2 (+4b)) =? (+3b)) (true)
:test ((log2 (+32b)) =? (+6b)) (true)
:test ((log2 (+48b)) =? (+6b)) (true)