Imaginary base numbers are a non-standard positional numeral system which uses an imaginary number as its radix. The most common is quater-imaginary with radix 2i.

The quater-imaginary numeral system was first proposed by Donald Knuth in 1955 as a submission for a high school science talent search. [Ref.]

Other imaginary bases are possible too but are not as widely discussed and aren't specifically named.

Task: Write a set of procedures (functions, subroutines, however they are referred to in your language) to convert base 10 numbers to an imaginary base and back.

At a minimum, support quater-imaginary (base 2i).

For extra kudos, support positive or negative bases 2i through 6i (or higher).

As a stretch goal, support converting non-integer numbers ( E.G. 227.65625+10.859375i ) to an imaginary base.

See Wikipedia: Quater-imaginary_base for more details.

For reference, here are some some decimal and complex numbers converted to quater-imaginary.






Base 10
Base 2i


1
 1


2
 2


3
 3


4
 10300


5
 10301


6
 10302


7
 10303


8
 10200


9
 10201


10
 10202


11
 10203


12
 10100


13
 10101


14
 10102


15
 10103


16
 10000






Base 10
Base 2i


−1
 103


−2
 102


−3
 101


−4
 100


−5
 203


−6
 202


−7
 201


−8
 200


−9
 303


−10
 302


−11
 301


−12
 300


−13
 1030003


−14
 1030002


−15
 1030001


−16
 1030000






Base 10
Base 2i


1i
10.2


2i
10.0


3i
20.2


4i
20.0


5i
30.2


6i
30.0


7i
103000.2


8i
103000.0


9i
103010.2


10i
103010.0


11i
103020.2


12i
103020.0


13i
103030.2


14i
103030.0


15i
102000.2


16i
102000.0






Base 10
Base 2i


−1i
0.2


−2i
1030.0


−3i
1030.2


−4i
1020.0


−5i
1020.2


−6i
1010.0


−7i
1010.2


−8i
1000.0


−9i
1000.2


−10i
2030.0


−11i
2030.2


−12i
2020.0


−13i
2020.2


−14i
2010.0


−15i
2010.2


−16i
2000.0





// version 1.2.10
 
import kotlin.math.ceil
 
class Complex(val real: Double, val imag: Double) {
 
    constructor(r: Int, i: Int) : this(r.toDouble(), i.toDouble())
 
    operator fun plus(other: Complex) = Complex(real + other.real, imag + other.imag)
 
    operator fun times(other: Complex) = Complex(
        real * other.real - imag * other.imag,
        real * other.imag + imag * other.real
    )
 
    operator fun times(other: Double) = Complex(real * other, imag * other)
 
    fun inv(): Complex {
        val denom = real * real + imag * imag
        return Complex(real / denom, -imag / denom)
    }
 
    operator fun unaryMinus() = Complex(-real, -imag)
 
    operator fun minus(other: Complex) = this + (-other)
 
    operator fun div(other: Complex) = this * other.inv()
 
    // only works properly if 'real' and 'imag' are both integral
    fun toQuaterImaginary(): QuaterImaginary {
        if (real == 0.0 && imag == 0.0) return QuaterImaginary("0")
        var re = real.toInt()
        var im = imag.toInt()
        var fi = -1
        val sb = StringBuilder()
        while (re != 0) {
            var rem = re % -4
            re /= -4
            if (rem < 0) {
                rem = 4 + rem
                re++
            }
            sb.append(rem)
            sb.append(0)
        }
        if (im != 0) {
            var f = (Complex(0.0, imag) / Complex(0.0, 2.0)).real
            im = ceil(f).toInt()
            f = -4.0 * (f - im.toDouble())
            var index = 1
            while (im != 0) {
                var rem = im % -4
                im /= -4
                if (rem < 0) {
                    rem = 4 + rem
                    im++
                }
                if (index < sb.length) {
                    sb[index] = (rem + 48).toChar()
                }
                else {
                    sb.append(0)
                    sb.append(rem)
                }
                index += 2
            }
            fi = f.toInt()
        }
        sb.reverse()
        if (fi != -1) sb.append(".$fi")
        var s = sb.toString().trimStart('0')
        if (s.startsWith(".")) s = "0$s"
        return QuaterImaginary(s)
    }
 
    override fun toString(): String {
        val real2 = if (real == -0.0) 0.0 else real  // get rid of negative zero
        val imag2 = if (imag == -0.0) 0.0 else imag  // ditto
        var result = if (imag2 >= 0.0) "$real2 + ${imag2}i" else "$real2 - ${-imag2}i"
        result = result.replace(".0 ", " ").replace(".0i", "i").replace(" + 0i", "")
        if (result.startsWith("0 + ")) result = result.drop(4)
        if (result.startsWith("0 - ")) result = "-" + result.drop(4)
        return result
    }
}
 
class QuaterImaginary(val b2i: String) {
 
    init {
        if (b2i == "" || !b2i.all { it in "0123." } || b2i.count { it == '.'} > 1 )
            throw RuntimeException("Invalid Base 2i number")
    }
 
    fun toComplex(): Complex {
        val pointPos = b2i.indexOf(".")
        var posLen = if (pointPos != -1) pointPos else b2i.length
        var sum = Complex(0.0, 0.0)
        var prod = Complex(1.0, 0.0)
        for (j in 0 until posLen) {
            val k = (b2i[posLen - 1 - j] - '0').toDouble()
            if (k > 0.0) sum += prod * k
            prod *= twoI
        }
        if (pointPos != -1) {
            prod = invTwoI
            for (j in posLen + 1 until b2i.length) {
                val k = (b2i[j] - '0').toDouble()
                if (k > 0.0) sum += prod * k
                prod *= invTwoI
            }
        }
        return sum
    }
 
    override fun toString() = b2i
 
    companion object {
        val twoI = Complex(0.0, 2.0)
        val invTwoI = twoI.inv()
    }
}
 
fun main(args: Array<String>) { 
    val fmt = "%4s -> %8s -> %4s"   
    for (i in 1..16) {
        var c1 = Complex(i, 0)
        var qi = c1.toQuaterImaginary()
        var c2 = qi.toComplex()
        print("$fmt     ".format(c1, qi, c2))
        c1 = -c1
        qi = c1.toQuaterImaginary()
        c2 = qi.toComplex()
        println(fmt.format(c1, qi, c2))
    }
    println()
    for (i in 1..16) {
        var c1 = Complex(0, i)
        var qi = c1.toQuaterImaginary()
        var c2 = qi.toComplex()
        print("$fmt     ".format(c1, qi, c2))
        c1 = -c1
        qi = c1.toQuaterImaginary()
        c2 = qi.toComplex()
        println(fmt.format(c1, qi, c2))
    }
}