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





import Base.show, Base.parse, Base.+, Base.-, Base.*, Base./, Base.^
 
function inbase4(charvec::Vector)
    if (!all(x -> x in ['-', '0', '1', '2', '3', '.'], charvec)) ||
        ((x = findlast(x -> x == '-', charvec)) != nothing && x > findfirst(x -> x != '-', charvec)) ||
        ((x = findall(x -> x == '.', charvec)) != nothing && length(x) > 1)
        return false
    end
    true
end
inbase4(s::String) = inbase4(split(s, ""))
 
abstract type ImaginaryBaseNumber <: Number end
 
struct QuaterImaginary <: ImaginaryBaseNumber
    cvector::Vector{Char}
    isnegative::Bool
end
 
function QuaterImaginary(charvec::Vector{Char})
    isneg = false
    if !inbase4(charvec)
        throw("Constructor vector for QuaterImaginary ($charvec) is not base 2i")
    elseif (i = length(findall(x -> x == '-', charvec))) > 0
        isneg = (-1) ^ i == -1
    end
    while length(charvec) > 1 && charvec[1] == '0' && charvec[2] != '.'
        popfirst!(charvec)
    end
    if (i = findfirst(x -> x == '.', charvec)) != nothing
        while length(charvec) > 3 && charvec[end] == '0' && charvec[end-1] != '.'
            pop!(charvec)
        end
    end
    if charvec[1] == '.'
            pushfirst!(charvec, '0')
    end
    if charvec[end] == '.'
        pop!(charvec)
    end
    QuaterImaginary(filter!(x -> x in ['0', '1', '2', '3', '.'], charvec), isneg)
end
 
function QuaterImaginary(s::String = "0")
    if match(r"^-?[0123\.]+$", s) == nothing
        throw("String constructor argument <$s> for QuaterImaginary is not base 2i")
    end
    QuaterImaginary([s[i] for i in 1:length(s)])
end
 
show(io::IO, qim::QuaterImaginary) = print(io, qim.isnegative ? "-" : "", join(qim.cvector, ""))
 
function parse(QuaterImaginary, x::Complex)
    sb = Vector{Char}()
    rea, ima = Int(floor(real(x))), Int(floor(imag(x)))
    if floor(real(x)) != rea || floor(imag(x)) != ima
        throw("Non-integer real and complex portions of complex numbers are not supported for QuaterImaginary")
    elseif rea == 0 == ima
        return QuaterImaginary(['0'])
    else
        fi = -1
        while rea != 0
            rea, rem = divrem(rea, -4)
            if rem < 0
                rem += 4
                rea += 1
            end
            push!(sb, Char(rem + '0'), '0')
        end
        if ima != 0
            f = real((ima * im)/(2im))
            ima = Int(ceil(f))
            f = -4.0 * (f - ima)
            idx = 1
            while ima != 0
                ima, rem = divrem(ima, -4)
                if rem < 0
                    rem += 4
                    ima += 1
                end
                if idx < length(sb)
                    sb[idx + 1] = Char(rem + '0')
                else
                    push!(sb, '0', Char(rem + '0'))
                end
                idx += 2
            end
            fi = Int(floor(f))
        end
        sb = reverse(sb)
        if fi != -1
            push!(sb, '.')
            append!(sb, map(x -> x[1], split(string(fi), "")))
        end
    end
    QuaterImaginary(sb)
end
 
function parse(Complex, qim::QuaterImaginary)
    pointpos = ((x = indexin('.', qim.cvector))[1] == nothing) ? -1 : x[1]
    poslen = (pointpos != -1) ? pointpos : length(qim.cvector) + 1
    qsum = 0.0 + 0.0im
    prod = 1.0 + 0.0im
    for j in 1:poslen-1
        k = Float64(qim.cvector[poslen - j] - '0')
        if k > 0.0
            qsum += prod * k
        end
        prod *= 2im
    end
    if pointpos != -1
        prod = inv(2im)
        for j in poslen+1:length(qim.cvector)
            k = Float64(qim.cvector[j] - '0')
            if k > 0.0
                qsum += prod * k
            end
            prod *= inv(2im)
        end
    end
    qsum
end
 
function testquim()
    function printcqc(c)
        q = parse(QuaterImaginary, Complex(c))
        c2 = parse(Complex, q)
        if imag(c2) == 0
            c2 = Int(c2)
        end
        print(lpad(c, 10), " -> ", lpad(q, 10), " -> ", lpad(c2, 12))
    end
    for i in 1:16
        printcqc(i)
        print("       ")
        printcqc(-i)
        println()
    end
    println()
    for i in 1:16
        c1 = Complex(0, i)
        printcqc(c1)
        print("       ")
        printcqc(-c1)
        println()
    end
end
 
QuaterImaginary(c::Complex) = parse(QuaterImaginary, c)
Complex(q::QuaterImaginary) = parse(Complex, q)
 
+(q1::QuaterImaginary, q2::QuaterImaginary) = QuaterImaginary(Complex(q1) + Complex(q2))
+(q1::Complex, q2::QuaterImaginary) = q1 + Complex(q2)
+(q1::QuaterImaginary, q2::Complex) = Complex(q1) + q2
-(q1::QuaterImaginary, q2::QuaterImaginary) = QuaterImaginary(Complex(q1) - Complex(q2))
-(q1::Complex, q2::QuaterImaginary) = q1 - Complex(q2)
-(q1::QuaterImaginary, q2::Complex) = Complex(q1) - q2
*(q1::QuaterImaginary, q2::QuaterImaginary) = QuaterImaginary(Complex(q1) * Complex(q2))
*(q1::Complex, q2::QuaterImaginary) = q1 * Complex(q2)
*(q1::QuaterImaginary, q2::Complex) = Complex(q1) * q2
/(q1::QuaterImaginary, q2::QuaterImaginary) = QuaterImaginary(Complex(q1) / Complex(q2))
/(q1::Complex, q2::QuaterImaginary) = q1 / Complex(q2)
/(q1::QuaterImaginary, q2::Complex) = Complex(q1) / q2
^(q1::QuaterImaginary, q2::QuaterImaginary) = QuaterImaginary(Complex(q1) ^ Complex(q2))
^(q1::Complex, q2::QuaterImaginary) = q1 ^ Complex(q2)
^(q1::QuaterImaginary, q2::Complex) = Complex(q1) ^ q2
 
testquim()
 