Imaginary base numbers

From Rosetta Code
Task
Imaginary base numbers
You are encouraged to solve this task according to the task description, using any language you may know.

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.

11l

Translation of: Python
F inv(c)
   V denom = c.real * c.real + c.imag * c.imag
   R Complex(c.real / denom, -c.imag / denom)

T QuaterImaginary
   :twoI = Complex(0, 2)
   :invTwoI = inv(.:twoI)

   String b2i

   F (str)
      I !re:‘[0123.]+’.match(str) | str.count(‘.’) > 1
         assert(0B, ‘Invalid base 2i number’)
      .b2i = str

   F toComplex()
      V pointPos = .b2i.findi(‘.’)
      V posLen = I (pointPos < 0) {.b2i.len} E pointPos
      V sum = Complex(0, 0)
      V prod = Complex(1, 0)
      L(j) 0 .< posLen
         V k = Int(.b2i[posLen - 1 - j])
         I k > 0
            sum += prod * k
         prod *= .:twoI
      I pointPos != -1
         prod = .:invTwoI
         L(j) posLen + 1 .< .b2i.len
            V k = Int(.b2i[j])
            I k > 0
               sum += prod * k
            prod *= .:invTwoI
      R sum

   F String()
      R String(.b2i)

F toQuaterImaginary(c)
   I c.real == 0.0 & c.imag == 0.0
      R QuaterImaginary(‘0’)

   V re = Int(c.real)
   V im = Int(c.imag)
   V fi = -1
   V ss = ‘’
   L re != 0
      (re, V rem) = divmod(re, -4)
      I rem < 0
         rem += 4
         re++
      ss ‘’= String(rem)‘0’
   I im != 0
      V f = c.imag / 2
      im = Int(ceil(f))
      f = -4 * (f - im)
      V index = 1
      L im != 0
         (im, V rem) = divmod(im, -4)
         I rem < 0
            rem += 4
            im++
         I index < ss.len
            assert(0B)
         E
            ss ‘’= ‘0’String(rem)
         index = index + 2
      fi = Int(f)
   ss = reversed(ss)
   I fi != -1
      ss ‘’= ‘.’String(fi)
   ss = ss.ltrim(‘0’)
   I ss[0] == ‘.’
      ss = ‘0’ss
   R QuaterImaginary(ss)

L(i) 1..16
   V c1 = Complex(i, 0)
   V qi = toQuaterImaginary(c1)
   V c2 = qi.toComplex()
   print(‘#8 -> #8 -> #8     ’.format(c1, qi, c2), end' ‘ ’)

   c1 = -c1
   qi = toQuaterImaginary(c1)
   c2 = qi.toComplex()
   print(‘#8 -> #8 -> #8’.format(c1, qi, c2))
print()

L(i) 1..16
   V c1 = Complex(0, i)
   V qi = toQuaterImaginary(c1)
   V c2 = qi.toComplex()
   print(‘#8 -> #8 -> #8     ’.format(c1, qi, c2), end' ‘ ’)

   c1 = -c1
   qi = toQuaterImaginary(c1)
   c2 = qi.toComplex()
   print(‘#8 -> #8 -> #8’.format(c1, qi, c2))

print(‘done’)
Output:
       1 ->        1 ->        1            -1 ->      103 ->       -1
       2 ->        2 ->        2            -2 ->      102 ->       -2
       3 ->        3 ->        3            -3 ->      101 ->       -3
       4 ->    10300 ->        4            -4 ->      100 ->       -4
       5 ->    10301 ->        5            -5 ->      203 ->       -5
       6 ->    10302 ->        6            -6 ->      202 ->       -6
       7 ->    10303 ->        7            -7 ->      201 ->       -7
       8 ->    10200 ->        8            -8 ->      200 ->       -8
       9 ->    10201 ->        9            -9 ->      303 ->       -9
      10 ->    10202 ->       10           -10 ->      302 ->      -10
      11 ->    10203 ->       11           -11 ->      301 ->      -11
      12 ->    10100 ->       12           -12 ->      300 ->      -12
      13 ->    10101 ->       13           -13 ->  1030003 ->      -13
      14 ->    10102 ->       14           -14 ->  1030002 ->      -14
      15 ->    10103 ->       15           -15 ->  1030001 ->      -15
      16 ->    10000 ->       16           -16 ->  1030000 ->      -16

      1i ->     10.2 ->       1i           -1i ->      0.2 ->      -1i
      2i ->     10.0 ->       2i           -2i ->   1030.0 ->      -2i
      3i ->     20.2 ->       3i           -3i ->   1030.2 ->      -3i
      4i ->     20.0 ->       4i           -4i ->   1020.0 ->      -4i
      5i ->     30.2 ->       5i           -5i ->   1020.2 ->      -5i
      6i ->     30.0 ->       6i           -6i ->   1010.0 ->      -6i
      7i -> 103000.2 ->       7i           -7i ->   1010.2 ->      -7i
      8i -> 103000.0 ->       8i           -8i ->   1000.0 ->      -8i
      9i -> 103010.2 ->       9i           -9i ->   1000.2 ->      -9i
     10i -> 103010.0 ->      10i          -10i ->   2030.0 ->     -10i
     11i -> 103020.2 ->      11i          -11i ->   2030.2 ->     -11i
     12i -> 103020.0 ->      12i          -12i ->   2020.0 ->     -12i
     13i -> 103030.2 ->      13i          -13i ->   2020.2 ->     -13i
     14i -> 103030.0 ->      14i          -14i ->   2010.0 ->     -14i
     15i -> 102000.2 ->      15i          -15i ->   2010.2 ->     -15i
     16i -> 102000.0 ->      16i          -16i ->   2000.0 ->     -16i
done

C

Translation of: C++
#include <math.h>
#include <stdio.h>
#include <string.h>

int find(char *s, char c) {
    for (char *i = s; *i != 0; i++) {
        if (*i == c) {
            return i - s;
        }
    }
    return -1;
}

void reverse(char *b, char *e) {
    for (e--; b < e; b++, e--) {
        char t = *b;
        *b = *e;
        *e = t;
    }
}

//////////////////////////////////////////////////////

struct Complex {
    double rel, img;
};

void printComplex(struct Complex c) {
    printf("(%3.0f + %3.0fi)", c.rel, c.img);
}

struct Complex makeComplex(double rel, double img) {
    struct Complex c = { rel, img };
    return c;
}

struct Complex addComplex(struct Complex a, struct Complex b) {
    struct Complex c = { a.rel + b.rel, a.img + b.img };
    return c;
}

struct Complex mulComplex(struct Complex a, struct Complex b) {
    struct Complex c = { a.rel * b.rel - a.img * b.img, a.rel * b.img - a.img * b.rel };
    return c;
}

struct Complex mulComplexD(struct Complex a, double b) {
    struct Complex c = { a.rel * b, a.img * b };
    return c;
}

struct Complex negComplex(struct Complex a) {
    return mulComplexD(a, -1.0);
}

struct Complex divComplex(struct Complex a, struct Complex b) {
    double re = a.rel * b.rel + a.img * b.img;
    double im = a.img * b.rel - a.rel * b.img;
    double d = b.rel * b.rel + b.img * b.img;
    struct Complex c = { re / d, im / d };
    return c;
}

struct Complex inv(struct Complex c) {
    double d = c.rel * c.rel + c.img * c.img;
    struct Complex i = { c.rel / d, -c.img / d };
    return i;
}

const struct Complex TWO_I = { 0.0, 2.0 };
const struct Complex INV_TWO_I = { 0.0, -0.5 };

//////////////////////////////////////////////////////

struct QuaterImaginary {
    char *b2i;
    int valid;
};

struct QuaterImaginary makeQuaterImaginary(char *s) {
    struct QuaterImaginary qi = { s, 0 }; // assume invalid until tested
    size_t i, valid = 1, cnt = 0;

    if (*s != 0) {
        for (i = 0; s[i] != 0; i++) {
            if (s[i] < '0' || '3' < s[i]) {
                if (s[i] == '.') {
                    cnt++;
                } else {
                    valid = 0;
                    break;
                }
            }
        }
        if (valid && cnt > 1) {
            valid = 0;
        }
    }

    qi.valid = valid;
    return qi;
}

void printQuaterImaginary(struct QuaterImaginary qi) {
    if (qi.valid) {
        printf("%8s", qi.b2i);
    } else {
        printf(" ERROR  ");
    }
}

//////////////////////////////////////////////////////

struct Complex qi2c(struct QuaterImaginary qi) {
    size_t len = strlen(qi.b2i);
    int pointPos = find(qi.b2i, '.');
    size_t posLen = (pointPos > 0) ? pointPos : len;
    struct Complex sum = makeComplex(0.0, 0.0);
    struct Complex prod = makeComplex(1.0, 0.0);
    size_t j;

    for (j = 0; j < posLen; j++) {
        double k = qi.b2i[posLen - 1 - j] - '0';
        if (k > 0.0) {
            sum = addComplex(sum, mulComplexD(prod, k));
        }
        prod = mulComplex(prod, TWO_I);
    }
    if (pointPos != -1) {
        prod = INV_TWO_I;
        for (j = posLen + 1; j < len; j++) {
            double k = qi.b2i[j] - '0';
            if (k > 0.0) {
                sum = addComplex(sum, mulComplexD(prod, k));
            }
            prod = mulComplex(prod, INV_TWO_I);
        }
    }
    return sum;
}

// only works properly if the real and imaginary parts are integral
struct QuaterImaginary c2qi(struct Complex c, char *out) {
    char *p = out;
    int re, im, fi;

    *p = 0;
    if (c.rel == 0.0 && c.img == 0.0) {
        return makeQuaterImaginary("0");
    }

    re = (int)c.rel;
    im = (int)c.img;
    fi = -1;
    while (re != 0) {
        int rem = re % -4;
        re /= -4;
        if (rem < 0) {
            rem += 4;
            re++;
        }
        *p++ = rem + '0';
        *p++ = '0';
        *p = 0;
    }
    if (im != 0) {
        size_t index = 1;
        struct Complex fc = divComplex((struct Complex) { 0.0, c.img }, (struct Complex) { 0.0, 2.0 });
        double f = fc.rel;
        im = (int)ceil(f);
        f = -4.0 * (f - im);
        while (im != 0) {
            int rem = im % -4;
            im /= -4;
            if (rem < 0) {
                rem += 4;
                im++;
            }
            if (index < (p - out)) {
                out[index] = rem + '0';
            } else {
                *p++ = '0';
                *p++ = rem + '0';
                *p = 0;
            }
            index += 2;
        }
        fi = (int)f;
    }

    reverse(out, p);
    if (fi != -1) {
        *p++ = '.';
        *p++ = fi + '0';
        *p = 0;
    }
    while (out[0] == '0' && out[1] != '.') {
        size_t i;
        for (i = 0; out[i] != 0; i++) {
            out[i] = out[i + 1];
        }
    }
    if (*out == '.') {
        reverse(out, p);
        *p++ = '0';
        *p = 0;
        reverse(out, p);
    }
    return makeQuaterImaginary(out);
}

//////////////////////////////////////////////////////

int main() {
    char buffer[16];
    int i;

    for (i = 1; i <= 16; i++) {
        struct Complex c1 = { i, 0.0 };
        struct QuaterImaginary qi = c2qi(c1, buffer);
        struct Complex c2 = qi2c(qi);
        printComplex(c1);
        printf(" -> ");
        printQuaterImaginary(qi);
        printf(" -> ");
        printComplex(c2);

        printf("     ");

        c1 = negComplex(c1);
        qi = c2qi(c1, buffer);
        c2 = qi2c(qi);
        printComplex(c1);
        printf(" -> ");
        printQuaterImaginary(qi);
        printf(" -> ");
        printComplex(c2);

        printf("\n");
    }

    printf("\n");

    for (i = 1; i <= 16; i++) {
        struct Complex c1 = { 0.0, i };
        struct QuaterImaginary qi = c2qi(c1, buffer);
        struct Complex c2 = qi2c(qi);
        printComplex(c1);
        printf(" -> ");
        printQuaterImaginary(qi);
        printf(" -> ");
        printComplex(c2);

        printf("     ");

        c1 = negComplex(c1);
        qi = c2qi(c1, buffer);
        c2 = qi2c(qi);
        printComplex(c1);
        printf(" -> ");
        printQuaterImaginary(qi);
        printf(" -> ");
        printComplex(c2);

        printf("\n");
    }

    return 0;
}
Output:
(  1 +   0i) ->        1 -> (  1 +   0i)     ( -1 +  -0i) ->      103 -> ( -1 +   0i)
(  2 +   0i) ->        2 -> (  2 +   0i)     ( -2 +  -0i) ->      102 -> ( -2 +   0i)
(  3 +   0i) ->        3 -> (  3 +   0i)     ( -3 +  -0i) ->      101 -> ( -3 +   0i)
(  4 +   0i) ->    10300 -> (  4 +   0i)     ( -4 +  -0i) ->      100 -> ( -4 +   0i)
(  5 +   0i) ->    10301 -> (  5 +   0i)     ( -5 +  -0i) ->      203 -> ( -5 +   0i)
(  6 +   0i) ->    10302 -> (  6 +   0i)     ( -6 +  -0i) ->      202 -> ( -6 +   0i)
(  7 +   0i) ->    10303 -> (  7 +   0i)     ( -7 +  -0i) ->      201 -> ( -7 +   0i)
(  8 +   0i) ->    10200 -> (  8 +   0i)     ( -8 +  -0i) ->      200 -> ( -8 +   0i)
(  9 +   0i) ->    10201 -> (  9 +   0i)     ( -9 +  -0i) ->      303 -> ( -9 +   0i)
( 10 +   0i) ->    10202 -> ( 10 +   0i)     (-10 +  -0i) ->      302 -> (-10 +   0i)
( 11 +   0i) ->    10203 -> ( 11 +   0i)     (-11 +  -0i) ->      301 -> (-11 +   0i)
( 12 +   0i) ->    10100 -> ( 12 +   0i)     (-12 +  -0i) ->      300 -> (-12 +   0i)
( 13 +   0i) ->    10101 -> ( 13 +   0i)     (-13 +  -0i) ->  1030003 -> (-13 +   0i)
( 14 +   0i) ->    10102 -> ( 14 +   0i)     (-14 +  -0i) ->  1030002 -> (-14 +   0i)
( 15 +   0i) ->    10103 -> ( 15 +   0i)     (-15 +  -0i) ->  1030001 -> (-15 +   0i)
( 16 +   0i) ->    10000 -> ( 16 +   0i)     (-16 +  -0i) ->  1030000 -> (-16 +   0i)

(  0 +   1i) ->     10.2 -> (  0 +   1i)     ( -0 +  -1i) ->      0.2 -> (  0 +  -1i)
(  0 +   2i) ->     10.0 -> (  0 +   2i)     ( -0 +  -2i) ->   1030.0 -> (  0 +  -2i)
(  0 +   3i) ->     20.2 -> (  0 +   3i)     ( -0 +  -3i) ->   1030.2 -> (  0 +  -3i)
(  0 +   4i) ->     20.0 -> (  0 +   4i)     ( -0 +  -4i) ->   1020.0 -> (  0 +  -4i)
(  0 +   5i) ->     30.2 -> (  0 +   5i)     ( -0 +  -5i) ->   1020.2 -> (  0 +  -5i)
(  0 +   6i) ->     30.0 -> (  0 +   6i)     ( -0 +  -6i) ->   1010.0 -> (  0 +  -6i)
(  0 +   7i) -> 103000.2 -> (  0 +   7i)     ( -0 +  -7i) ->   1010.2 -> (  0 +  -7i)
(  0 +   8i) -> 103000.0 -> (  0 +   8i)     ( -0 +  -8i) ->   1000.0 -> (  0 +  -8i)
(  0 +   9i) -> 103010.2 -> (  0 +   9i)     ( -0 +  -9i) ->   1000.2 -> (  0 +  -9i)
(  0 +  10i) -> 103010.0 -> (  0 +  10i)     ( -0 + -10i) ->   2030.0 -> (  0 + -10i)
(  0 +  11i) -> 103020.2 -> (  0 +  11i)     ( -0 + -11i) ->   2030.2 -> (  0 + -11i)
(  0 +  12i) -> 103020.0 -> (  0 +  12i)     ( -0 + -12i) ->   2020.0 -> (  0 + -12i)
(  0 +  13i) -> 103030.2 -> (  0 +  13i)     ( -0 + -13i) ->   2020.2 -> (  0 + -13i)
(  0 +  14i) -> 103030.0 -> (  0 +  14i)     ( -0 + -14i) ->   2010.0 -> (  0 + -14i)
(  0 +  15i) -> 102000.2 -> (  0 +  15i)     ( -0 + -15i) ->   2010.2 -> (  0 + -15i)
(  0 +  16i) -> 102000.0 -> (  0 +  16i)     ( -0 + -16i) ->   2000.0 -> (  0 + -16i)

C#

using System;
using System.Linq;
using System.Text;

namespace ImaginaryBaseNumbers {
    class Complex {
        private double real, imag;

        public Complex(int r, int i) {
            real = r;
            imag = i;
        }

        public Complex(double r, double i) {
            real = r;
            imag = i;
        }

        public static Complex operator -(Complex self) =>
            new Complex(-self.real, -self.imag);

        public static Complex operator +(Complex rhs, Complex lhs) =>
            new Complex(rhs.real + lhs.real, rhs.imag + lhs.imag);

        public static Complex operator -(Complex rhs, Complex lhs) =>
            new Complex(rhs.real - lhs.real, rhs.imag - lhs.imag);

        public static Complex operator *(Complex rhs, Complex lhs) =>
            new Complex(
                rhs.real * lhs.real - rhs.imag * lhs.imag,
                rhs.real * lhs.imag + rhs.imag * lhs.real
                );

        public static Complex operator *(Complex rhs, double lhs) =>
             new Complex(rhs.real * lhs, rhs.imag * lhs);

        public static Complex operator /(Complex rhs, Complex lhs) =>
            rhs * lhs.Inv();

        public Complex Inv() {
            double denom = real * real + imag * imag;
            return new Complex(real / denom, -imag / denom);
        }

        public QuaterImaginary ToQuaterImaginary() {
            if (real == 0.0 && imag == 0.0) return new QuaterImaginary("0");
            int re = (int)real;
            int im = (int)imag;
            int fi = -1;
            StringBuilder sb = new StringBuilder();
            while (re != 0) {
                int rem = re % -4;
                re /= -4;
                if (rem < 0) {
                    rem = 4 + rem;
                    re++;
                }
                sb.Append(rem);
                sb.Append(0);
            }
            if (im != 0) {
                double f = (new Complex(0.0, imag) / new Complex(0.0, 2.0)).real;
                im = (int)Math.Ceiling(f);
                f = -4.0 * (f - im);
                int index = 1;
                while (im != 0) {
                    int rem = im % -4;
                    im /= -4;
                    if (rem < 0) {
                        rem = 4 + rem;
                        im++;
                    }
                    if (index < sb.Length) {
                        sb[index] = (char)(rem + 48);
                    } else {
                        sb.Append(0);
                        sb.Append(rem);
                    }
                    index += 2;
                }
                fi = (int)f;
            }
            string reverse = new string(sb.ToString().Reverse().ToArray());
            sb.Length = 0;
            sb.Append(reverse);
            if (fi != -1) sb.AppendFormat(".{0}", fi);
            string s = sb.ToString().TrimStart('0');
            if (s[0] == '.') s = "0" + s;
            return new QuaterImaginary(s);
        }

        public override string ToString() {
            double real2 = (real == -0.0) ? 0.0 : real;  // get rid of negative zero
            double imag2 = (imag == -0.0) ? 0.0 : imag;  // ditto
            if (imag2 == 0.0) {
                return string.Format("{0}", real2);
            }
            if (real2 == 0.0) {
                return string.Format("{0}i", imag2);
            }
            if (imag2 > 0.0) {
                return string.Format("{0} + {1}i", real2, imag2);
            }
            return string.Format("{0} - {1}i", real2, -imag2);
        }
    }

    class QuaterImaginary {
        internal static Complex twoI = new Complex(0.0, 2.0);
        internal static Complex invTwoI = twoI.Inv();

        private string b2i;

        public QuaterImaginary(string b2i) {
            if (b2i == "" || !b2i.All(c => "0123.".IndexOf(c) > -1) || b2i.Count(c => c == '.') > 1) {
                throw new Exception("Invalid Base 2i number");
            }
            this.b2i = b2i;
        }

        public Complex ToComplex() {
            int pointPos = b2i.IndexOf(".");
            int posLen = (pointPos != -1) ? pointPos : b2i.Length;
            Complex sum = new Complex(0.0, 0.0);
            Complex prod = new Complex(1.0, 0.0);
            for (int j = 0; j < posLen; j++) {
                double k = (b2i[posLen - 1 - j] - '0');
                if (k > 0.0) {
                    sum += prod * k;
                }
                prod *= twoI;
            }
            if (pointPos != -1) {
                prod = invTwoI;
                for (int j = posLen + 1; j < b2i.Length; j++) {
                    double k = (b2i[j] - '0');
                    if (k > 0.0) {
                        sum += prod * k;
                    }
                    prod *= invTwoI;
                }
            }

            return sum;
        }

        public override string ToString() {
            return b2i;
        }
    }

    class Program {
        static void Main(string[] args) {
            for (int i = 1; i <= 16; i++) {
                Complex c1 = new Complex(i, 0);
                QuaterImaginary qi = c1.ToQuaterImaginary();
                Complex c2 = qi.ToComplex();
                Console.Write("{0,4} -> {1,8} -> {2,4}     ", c1, qi, c2);
                c1 = -c1;
                qi = c1.ToQuaterImaginary();
                c2 = qi.ToComplex();
                Console.WriteLine("{0,4} -> {1,8} -> {2,4}", c1, qi, c2);
            }
            Console.WriteLine();
            for (int i = 1; i <= 16; i++) {
                Complex c1 = new Complex(0, i);
                QuaterImaginary qi = c1.ToQuaterImaginary();
                Complex c2 = qi.ToComplex();
                Console.Write("{0,4} -> {1,8} -> {2,4}     ", c1, qi, c2);
                c1 = -c1;
                qi = c1.ToQuaterImaginary();
                c2 = qi.ToComplex();
                Console.WriteLine("{0,4} -> {1,8} -> {2,4}", c1, qi, c2);
            }
        }
    }
}
Output:
   1 ->        1 ->    1       -1 ->      103 ->   -1
   2 ->        2 ->    2       -2 ->      102 ->   -2
   3 ->        3 ->    3       -3 ->      101 ->   -3
   4 ->    10300 ->    4       -4 ->      100 ->   -4
   5 ->    10301 ->    5       -5 ->      203 ->   -5
   6 ->    10302 ->    6       -6 ->      202 ->   -6
   7 ->    10303 ->    7       -7 ->      201 ->   -7
   8 ->    10200 ->    8       -8 ->      200 ->   -8
   9 ->    10201 ->    9       -9 ->      303 ->   -9
  10 ->    10202 ->   10      -10 ->      302 ->  -10
  11 ->    10203 ->   11      -11 ->      301 ->  -11
  12 ->    10100 ->   12      -12 ->      300 ->  -12
  13 ->    10101 ->   13      -13 ->  1030003 ->  -13
  14 ->    10102 ->   14      -14 ->  1030002 ->  -14
  15 ->    10103 ->   15      -15 ->  1030001 ->  -15
  16 ->    10000 ->   16      -16 ->  1030000 ->  -16

  1i ->     10.2 ->   1i      -1i ->      0.2 ->  -1i
  2i ->     10.0 ->   2i      -2i ->   1030.0 ->  -2i
  3i ->     20.2 ->   3i      -3i ->   1030.2 ->  -3i
  4i ->     20.0 ->   4i      -4i ->   1020.0 ->  -4i
  5i ->     30.2 ->   5i      -5i ->   1020.2 ->  -5i
  6i ->     30.0 ->   6i      -6i ->   1010.0 ->  -6i
  7i -> 103000.2 ->   7i      -7i ->   1010.2 ->  -7i
  8i -> 103000.0 ->   8i      -8i ->   1000.0 ->  -8i
  9i -> 103010.2 ->   9i      -9i ->   1000.2 ->  -9i
 10i -> 103010.0 ->  10i     -10i ->   2030.0 -> -10i
 11i -> 103020.2 ->  11i     -11i ->   2030.2 -> -11i
 12i -> 103020.0 ->  12i     -12i ->   2020.0 -> -12i
 13i -> 103030.2 ->  13i     -13i ->   2020.2 -> -13i
 14i -> 103030.0 ->  14i     -14i ->   2010.0 -> -14i
 15i -> 102000.2 ->  15i     -15i ->   2010.2 -> -15i
 16i -> 102000.0 ->  16i     -16i ->   2000.0 -> -16i

C++

Translation of: C#
#include <algorithm>
#include <complex>
#include <iomanip>
#include <iostream>

std::complex<double> inv(const std::complex<double>& c) {
    double denom = c.real() * c.real() + c.imag() * c.imag();
    return std::complex<double>(c.real() / denom, -c.imag() / denom);
}

class QuaterImaginary {
public:
    QuaterImaginary(const std::string& s) : b2i(s) {
        static std::string base("0123.");

        if (b2i.empty()
            || std::any_of(s.cbegin(), s.cend(), [](char c) { return base.find(c) == std::string::npos; })
            || std::count(s.cbegin(), s.cend(), '.') > 1) {
            throw std::runtime_error("Invalid base 2i number");
        }
    }

    QuaterImaginary& operator=(const QuaterImaginary& q) {
        b2i = q.b2i;
        return *this;
    }

    std::complex<double> toComplex() const {
        int pointPos = b2i.find('.');
        int posLen = (pointPos != std::string::npos) ? pointPos : b2i.length();
        std::complex<double> sum(0.0, 0.0);
        std::complex<double> prod(1.0, 0.0);
        for (int j = 0; j < posLen; j++) {
            double k = (b2i[posLen - 1 - j] - '0');
            if (k > 0.0) {
                sum += prod * k;
            }
            prod *= twoI;
        }
        if (pointPos != -1) {
            prod = invTwoI;
            for (size_t j = posLen + 1; j < b2i.length(); j++) {
                double k = (b2i[j] - '0');
                if (k > 0.0) {
                    sum += prod * k;
                }
                prod *= invTwoI;
            }
        }

        return sum;
    }

    friend std::ostream& operator<<(std::ostream&, const QuaterImaginary&);

private:
    const std::complex<double> twoI{ 0.0, 2.0 };
    const std::complex<double> invTwoI = inv(twoI);

    std::string b2i;
};

std::ostream& operator<<(std::ostream& os, const QuaterImaginary& q) {
    return os << q.b2i;
}

// only works properly if 'real' and 'imag' are both integral
QuaterImaginary toQuaterImaginary(const std::complex<double>& c) {
    if (c.real() == 0.0 && c.imag() == 0.0) return QuaterImaginary("0");

    int re = (int)c.real();
    int im = (int)c.imag();
    int fi = -1;
    std::stringstream ss;
    while (re != 0) {
        int rem = re % -4;
        re /= -4;
        if (rem < 0) {
            rem = 4 + rem;
            re++;
        }
        ss << rem << 0;
    }
    if (im != 0) {
        double f = (std::complex<double>(0.0, c.imag()) / std::complex<double>(0.0, 2.0)).real();
        im = (int)ceil(f);
        f = -4.0 * (f - im);
        size_t index = 1;
        while (im != 0) {
            int rem = im % -4;
            im /= -4;
            if (rem < 0) {
                rem = 4 + rem;
                im++;
            }
            if (index < ss.str().length()) {
                ss.str()[index] = (char)(rem + 48);
            } else {
                ss << 0 << rem;
            }
            index += 2;
        }
        fi = (int)f;
    }

    auto r = ss.str();
    std::reverse(r.begin(), r.end());
    ss.str("");
    ss.clear();
    ss << r;
    if (fi != -1) ss << '.' << fi;
    r = ss.str();
    r.erase(r.begin(), std::find_if(r.begin(), r.end(), [](char c) { return c != '0'; }));
    if (r[0] == '.')r = "0" + r;
    return QuaterImaginary(r);
}

int main() {
    using namespace std;

    for (int i = 1; i <= 16; i++) {
        complex<double> c1(i, 0);
        QuaterImaginary qi = toQuaterImaginary(c1);
        complex<double> c2 = qi.toComplex();
        cout << setw(8) << c1 << " -> " << setw(8) << qi << " -> " << setw(8) << c2 << "     ";
        c1 = -c1;
        qi = toQuaterImaginary(c1);
        c2 = qi.toComplex();
        cout << setw(8) << c1 << " -> " << setw(8) << qi << " -> " << setw(8) << c2 << endl;
    }
    cout << endl;

    for (int i = 1; i <= 16; i++) {
        complex<double> c1(0, i);
        QuaterImaginary qi = toQuaterImaginary(c1);
        complex<double> c2 = qi.toComplex();
        cout << setw(8) << c1 << " -> " << setw(8) << qi << " -> " << setw(8) << c2 << "     ";
        c1 = -c1;
        qi = toQuaterImaginary(c1);
        c2 = qi.toComplex();
        cout << setw(8) << c1 << " -> " << setw(8) << qi << " -> " << setw(8) << c2 << endl;
    }

    return 0;
}
Output:
   (1,0) ->        1 ->    (1,0)      (-1,-0) ->      103 ->   (-1,0)
   (2,0) ->        2 ->    (2,0)      (-2,-0) ->      102 ->   (-2,0)
   (3,0) ->        3 ->    (3,0)      (-3,-0) ->      101 ->   (-3,0)
   (4,0) ->    10300 ->    (4,0)      (-4,-0) ->      100 ->   (-4,0)
   (5,0) ->    10301 ->    (5,0)      (-5,-0) ->      203 ->   (-5,0)
   (6,0) ->    10302 ->    (6,0)      (-6,-0) ->      202 ->   (-6,0)
   (7,0) ->    10303 ->    (7,0)      (-7,-0) ->      201 ->   (-7,0)
   (8,0) ->    10200 ->    (8,0)      (-8,-0) ->      200 ->   (-8,0)
   (9,0) ->    10201 ->    (9,0)      (-9,-0) ->      303 ->   (-9,0)
  (10,0) ->    10202 ->   (10,0)     (-10,-0) ->      302 ->  (-10,0)
  (11,0) ->    10203 ->   (11,0)     (-11,-0) ->      301 ->  (-11,0)
  (12,0) ->    10100 ->   (12,0)     (-12,-0) ->      300 ->  (-12,0)
  (13,0) ->    10101 ->   (13,0)     (-13,-0) ->  1030003 ->  (-13,0)
  (14,0) ->    10102 ->   (14,0)     (-14,-0) ->  1030002 ->  (-14,0)
  (15,0) ->    10103 ->   (15,0)     (-15,-0) ->  1030001 ->  (-15,0)
  (16,0) ->    10000 ->   (16,0)     (-16,-0) ->  1030000 ->  (-16,0)

   (0,1) ->     10.2 ->    (0,1)      (-0,-1) ->      0.2 ->   (0,-1)
   (0,2) ->     10.0 ->    (0,2)      (-0,-2) ->   1030.0 ->   (0,-2)
   (0,3) ->     20.2 ->    (0,3)      (-0,-3) ->   1030.2 ->   (0,-3)
   (0,4) ->     20.0 ->    (0,4)      (-0,-4) ->   1020.0 ->   (0,-4)
   (0,5) ->     30.2 ->    (0,5)      (-0,-5) ->   1020.2 ->   (0,-5)
   (0,6) ->     30.0 ->    (0,6)      (-0,-6) ->   1010.0 ->   (0,-6)
   (0,7) -> 103000.2 ->    (0,7)      (-0,-7) ->   1010.2 ->   (0,-7)
   (0,8) -> 103000.0 ->    (0,8)      (-0,-8) ->   1000.0 ->   (0,-8)
   (0,9) -> 103010.2 ->    (0,9)      (-0,-9) ->   1000.2 ->   (0,-9)
  (0,10) -> 103010.0 ->   (0,10)     (-0,-10) ->   2030.0 ->  (0,-10)
  (0,11) -> 103020.2 ->   (0,11)     (-0,-11) ->   2030.2 ->  (0,-11)
  (0,12) -> 103020.0 ->   (0,12)     (-0,-12) ->   2020.0 ->  (0,-12)
  (0,13) -> 103030.2 ->   (0,13)     (-0,-13) ->   2020.2 ->  (0,-13)
  (0,14) -> 103030.0 ->   (0,14)     (-0,-14) ->   2010.0 ->  (0,-14)
  (0,15) -> 102000.2 ->   (0,15)     (-0,-15) ->   2010.2 ->  (0,-15)
  (0,16) -> 102000.0 ->   (0,16)     (-0,-16) ->   2000.0 ->  (0,-16)

D

Translation of: Kotlin
import std.algorithm;
import std.array;
import std.complex;
import std.conv;
import std.format;
import std.math;
import std.stdio;
import std.string;

Complex!double inv(Complex!double v) {
    auto denom = v.re*v.re + v.im*v.im;
    return v.conj / denom;
}

QuaterImaginary toQuaterImaginary(Complex!double v) {
    if (v.re == 0.0 && v.im == 0.0) return QuaterImaginary("0");
    auto re = v.re.to!int;
    auto im = v.im.to!int;
    auto fi = -1;
    auto sb = appender!(char[]);
    while (re != 0) {
        auto rem = re % -4;
        re /= -4;
        if (rem < 0) {
            rem = 4 + rem;
            re++;
        }
        sb.formattedWrite("%d", rem);
        sb.put("0");
    }
    if (im != 0) {
        auto f = (complex(0.0, v.im) / complex(0.0, 2.0)).re;
        im = f.ceil.to!int;
        f = -4.0 * (f - im.to!double);
        auto index = 1;
        while (im != 0) {
            auto rem = im % -4;
            im /= -4;
            if (rem < 0) {
                rem = 4 + rem;
                im++;
            }
            if (index < sb.data.length) {
                sb.data[index] = cast(char)(rem + '0');
            } else {
                sb.put("0");
                sb.formattedWrite("%d", rem);
            }
            index += 2;
        }
        fi = f.to!int;
    }
    sb.data.reverse;
    if (fi != -1) sb.formattedWrite(".%d", fi);
    int i;
    while (i < sb.data.length && sb.data[i] == '0') {
        i++;
    }
    auto s = sb.data[i..$].idup;
    if (s[0] == '.') s = "0" ~ s;
    return QuaterImaginary(s);
}

struct QuaterImaginary {
    private string b2i;

    this(string b2i) {
        if (b2i == "" || b2i.count('.') > 1) {
            throw new Exception("Invalid Base 2i number");
        }
        foreach (c; b2i) {
            if (!canFind("0123.", c)) {
                throw new Exception("Invalid Base 2i number");
            }
        }
        this.b2i = b2i;
    }

    T opCast(T : Complex!double)() {
        auto pointPos = b2i.indexOf('.');
        size_t posLen;
        if (pointPos != -1) {
            posLen = pointPos;
        } else {
            posLen = b2i.length;
        }
        auto sum = complex(0.0, 0.0);
        auto prod = complex(1.0, 0.0);
        foreach (j; 0..posLen) {
            auto k = (b2i[posLen - 1 - j] - '0').to!double;
            if (k > 0.0) {
                sum += prod * k;
            }
            prod *= twoI;
        }
        if (pointPos != -1) {
            prod = invTwoI;
            foreach (j; posLen+1..b2i.length) {
                auto k = (b2i[j] - '0').to!double;
                if (k > 0.0) {
                    sum += prod * k;
                }
                prod *= invTwoI;
            }
        }
        return sum;
    }

    void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const {
        if (fmt.spec == 's') {
            for (int i=0; i<fmt.width-b2i.length; ++i) {
                sink(" ");
            }
        }
        sink(b2i);
    }

    enum twoI = complex(0.0, 2.0);
    enum invTwoI = twoI.inv;
}

unittest {
    import std.exception;
    assertThrown!Exception(QuaterImaginary(""));
    assertThrown!Exception(QuaterImaginary("1.2.3"));
    assertThrown!Exception(QuaterImaginary("a"));
    assertThrown!Exception(QuaterImaginary("4"));
    assertThrown!Exception(QuaterImaginary(" "));
}

void main() {
    foreach (i; 1..17) {
        auto c1 = complex(i, 0);
        auto qi = c1.toQuaterImaginary;
        auto c2 = cast(Complex!double) qi;
        writef("%4s -> %8s -> %4s     ", c1.re, qi, c2.re);
        c1 = -c1;
        qi = c1.toQuaterImaginary();
        c2 = cast(Complex!double) qi;
        writefln("%4s -> %8s -> %4s", c1.re, qi, c2.re);
    }
    writeln;
    foreach (i; 1..17) {
        auto c1 = complex(0, i);
        auto qi = c1.toQuaterImaginary;
        auto c2 = qi.to!(Complex!double);
        writef("%4si -> %8s -> %4si     ", c1.im, qi, c2.im);
        c1 = -c1;
        qi = c1.toQuaterImaginary();
        c2 = cast(Complex!double) qi;
        writefln("%4si -> %8s -> %4si", c1.im, qi, c2.im);
    }
}
Output:
   1 ->        1 ->    1       -1 ->      103 ->   -1
   2 ->        2 ->    2       -2 ->      102 ->   -2
   3 ->        3 ->    3       -3 ->      101 ->   -3
   4 ->    10300 ->    4       -4 ->      100 ->   -4
   5 ->    10301 ->    5       -5 ->      203 ->   -5
   6 ->    10302 ->    6       -6 ->      202 ->   -6
   7 ->    10303 ->    7       -7 ->      201 ->   -7
   8 ->    10200 ->    8       -8 ->      200 ->   -8
   9 ->    10201 ->    9       -9 ->      303 ->   -9
  10 ->    10202 ->   10      -10 ->      302 ->  -10
  11 ->    10203 ->   11      -11 ->      301 ->  -11
  12 ->    10100 ->   12      -12 ->      300 ->  -12
  13 ->    10101 ->   13      -13 ->  1030003 ->  -13
  14 ->    10102 ->   14      -14 ->  1030002 ->  -14
  15 ->    10103 ->   15      -15 ->  1030001 ->  -15
  16 ->    10000 ->   16      -16 ->  1030000 ->  -16

  1i ->     10.2 ->   1i      -1i ->      0.2 ->  -1i
  2i ->     10.0 ->   2i      -2i ->   1030.0 ->  -2i
  3i ->     20.2 ->   3i      -3i ->   1030.2 ->  -3i
  4i ->     20.0 ->   4i      -4i ->   1020.0 ->  -4i
  5i ->     30.2 ->   5i      -5i ->   1020.2 ->  -5i
  6i ->     30.0 ->   6i      -6i ->   1010.0 ->  -6i
  7i -> 103000.2 ->   7i      -7i ->   1010.2 ->  -7i
  8i -> 103000.0 ->   8i      -8i ->   1000.0 ->  -8i
  9i -> 103010.2 ->   9i      -9i ->   1000.2 ->  -9i
 10i -> 103010.0 ->  10i     -10i ->   2030.0 -> -10i
 11i -> 103020.2 ->  11i     -11i ->   2030.2 -> -11i
 12i -> 103020.0 ->  12i     -12i ->   2020.0 -> -12i
 13i -> 103030.2 ->  13i     -13i ->   2020.2 -> -13i
 14i -> 103030.0 ->  14i     -14i ->   2010.0 -> -14i
 15i -> 102000.2 ->  15i     -15i ->   2010.2 -> -15i
 16i -> 102000.0 ->  16i     -16i ->   2000.0 -> -16i

Go

Translation of: Kotlin

... though a bit shorter as Go has support for complex numbers built into the language.

package main

import (
    "fmt"
    "math"
    "strconv"
    "strings"
)

const (
    twoI    = 2.0i
    invTwoI = 1.0 / twoI
)

type quaterImaginary struct {
    b2i string
}

func reverse(s string) string {
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r)
}

func newQuaterImaginary(b2i string) quaterImaginary {
    b2i = strings.TrimSpace(b2i)
    _, err := strconv.ParseFloat(b2i, 64)
    if err != nil {
        panic("invalid Base 2i number")
    }
    return quaterImaginary{b2i}
}

func toComplex(q quaterImaginary) complex128 {
    pointPos := strings.Index(q.b2i, ".")
    var posLen int
    if pointPos != -1 {
        posLen = pointPos
    } else {
        posLen = len(q.b2i)
    }
    sum := 0.0i
    prod := complex(1.0, 0.0)
    for j := 0; j < posLen; j++ {
        k := float64(q.b2i[posLen-1-j] - '0')
        if k > 0.0 {
            sum += prod * complex(k, 0.0)
        }
        prod *= twoI
    }
    if pointPos != -1 {
        prod = invTwoI
        for j := posLen + 1; j < len(q.b2i); j++ {
            k := float64(q.b2i[j] - '0')
            if k > 0.0 {
                sum += prod * complex(k, 0.0)
            }
            prod *= invTwoI
        }
    }
    return sum
}

func (q quaterImaginary) String() string {
    return q.b2i
}

// only works properly if 'real' and 'imag' are both integral
func toQuaterImaginary(c complex128) quaterImaginary {
    if c == 0i {
        return quaterImaginary{"0"}
    }
    re := int(real(c))
    im := int(imag(c))
    fi := -1
    var sb strings.Builder
    for re != 0 {
        rem := re % -4
        re /= -4
        if rem < 0 {
            rem += 4
            re++
        }
        sb.WriteString(strconv.Itoa(rem))
        sb.WriteString("0")
    }
    if im != 0 {
        f := real(complex(0.0, imag(c)) / 2.0i)
        im = int(math.Ceil(f))
        f = -4.0 * (f - float64(im))
        index := 1
        for im != 0 {
            rem := im % -4
            im /= -4
            if rem < 0 {
                rem += 4
                im++
            }
            if index < sb.Len() {
                bs := []byte(sb.String())
                bs[index] = byte(rem + 48)
                sb.Reset()
                sb.Write(bs)
            } else {
                sb.WriteString("0")
                sb.WriteString(strconv.Itoa(rem))
            }
            index += 2
        }
        fi = int(f)
    }
    s := reverse(sb.String())
    if fi != -1 {
        s = fmt.Sprintf("%s.%d", s, fi)
    }
    s = strings.TrimLeft(s, "0")
    if s[0] == '.' {
        s = "0" + s
    }
    return newQuaterImaginary(s)
}

func main() {
    for i := 1; i <= 16; i++ {
        c1 := complex(float64(i), 0.0)
        qi := toQuaterImaginary(c1)
        c2 := toComplex(qi)
        fmt.Printf("%4.0f -> %8s -> %4.0f     ", real(c1), qi, real(c2))
        c1 = -c1
        qi = toQuaterImaginary(c1)
        c2 = toComplex(qi)
        fmt.Printf("%4.0f -> %8s -> %4.0f\n", real(c1), qi, real(c2))
    }
    fmt.Println()
    for i := 1; i <= 16; i++ {
        c1 := complex(0.0, float64(i))
        qi := toQuaterImaginary(c1)
        c2 := toComplex(qi)
        fmt.Printf("%3.0fi -> %8s -> %3.0fi     ", imag(c1), qi, imag(c2))
        c1 = -c1
        qi = toQuaterImaginary(c1)
        c2 = toComplex(qi)
        fmt.Printf("%3.0fi -> %8s -> %3.0fi\n", imag(c1), qi, imag(c2))
    }
}
Output:
   1 ->        1 ->    1       -1 ->      103 ->   -1
   2 ->        2 ->    2       -2 ->      102 ->   -2
   3 ->        3 ->    3       -3 ->      101 ->   -3
   4 ->    10300 ->    4       -4 ->      100 ->   -4
   5 ->    10301 ->    5       -5 ->      203 ->   -5
   6 ->    10302 ->    6       -6 ->      202 ->   -6
   7 ->    10303 ->    7       -7 ->      201 ->   -7
   8 ->    10200 ->    8       -8 ->      200 ->   -8
   9 ->    10201 ->    9       -9 ->      303 ->   -9
  10 ->    10202 ->   10      -10 ->      302 ->  -10
  11 ->    10203 ->   11      -11 ->      301 ->  -11
  12 ->    10100 ->   12      -12 ->      300 ->  -12
  13 ->    10101 ->   13      -13 ->  1030003 ->  -13
  14 ->    10102 ->   14      -14 ->  1030002 ->  -14
  15 ->    10103 ->   15      -15 ->  1030001 ->  -15
  16 ->    10000 ->   16      -16 ->  1030000 ->  -16

  1i ->     10.2 ->   1i      -1i ->      0.2 ->  -1i
  2i ->     10.0 ->   2i      -2i ->   1030.0 ->  -2i
  3i ->     20.2 ->   3i      -3i ->   1030.2 ->  -3i
  4i ->     20.0 ->   4i      -4i ->   1020.0 ->  -4i
  5i ->     30.2 ->   5i      -5i ->   1020.2 ->  -5i
  6i ->     30.0 ->   6i      -6i ->   1010.0 ->  -6i
  7i -> 103000.2 ->   7i      -7i ->   1010.2 ->  -7i
  8i -> 103000.0 ->   8i      -8i ->   1000.0 ->  -8i
  9i -> 103010.2 ->   9i      -9i ->   1000.2 ->  -9i
 10i -> 103010.0 ->  10i     -10i ->   2030.0 -> -10i
 11i -> 103020.2 ->  11i     -11i ->   2030.2 -> -11i
 12i -> 103020.0 ->  12i     -12i ->   2020.0 -> -12i
 13i -> 103030.2 ->  13i     -13i ->   2020.2 -> -13i
 14i -> 103030.0 ->  14i     -14i ->   2010.0 -> -14i
 15i -> 102000.2 ->  15i     -15i ->   2010.2 -> -15i
 16i -> 102000.0 ->  16i     -16i ->   2000.0 -> -16i

Haskell

import Data.Char (chr, digitToInt, intToDigit, isDigit, ord)
import Data.Complex (Complex (..), imagPart, realPart)
import Data.List (delete, elemIndex)
import Data.Maybe (fromMaybe)

base :: Complex Float
base = 0 :+ 2

quotRemPositive :: Int -> Int -> (Int, Int)
quotRemPositive a b
  | r < 0 = (1 + q, floor (realPart (-base ^^ 2)) + r)
  | otherwise = (q, r)
  where
    (q, r) = quotRem a b

digitToIntQI :: Char -> Int
digitToIntQI c
  | isDigit c = digitToInt c
  | otherwise = ord c - ord 'a' + 10

shiftRight :: String -> String
shiftRight n
  | l == '0' = h
  | otherwise = h <> ('.' : [l])
  where
    (l, h) = (last n, init n)

intToDigitQI :: Int -> Char
intToDigitQI i
  | i `elem` [0 .. 9] = intToDigit i
  | otherwise = chr (i - 10 + ord 'a')

fromQItoComplex :: String -> Complex Float -> Complex Float
fromQItoComplex num b =
  let dot = fromMaybe (length num) (elemIndex '.' num)
   in fst $
        foldl
          ( \(a, indx) x ->
              ( a + fromIntegral (digitToIntQI x)
                  * (b ^^ (dot - indx)),
                indx + 1
              )
          )
          (0, 1)
          (delete '.' num)

euclidEr :: Int -> Int -> [Int] -> [Int]
euclidEr a b l
  | a == 0 = l
  | otherwise =
      let (q, r) = quotRemPositive a b
       in euclidEr q b (0 : r : l)

fromIntToQI :: Int -> [Int]
fromIntToQI 0 = [0]
fromIntToQI x =
  tail
    ( euclidEr
        x
        (floor $ realPart (base ^^ 2))
        []
    )

getCuid :: Complex Int -> Int
getCuid c = imagPart c * floor (imagPart (-base))

qizip :: Complex Int -> [Int]
qizip c =
  let (r, i) =
        ( fromIntToQI (realPart c) <> [0],
          fromIntToQI (getCuid c)
        )
   in let m = min (length r) (length i)
       in take (length r - m) r
            <> take (length i - m) i
            <> reverse
              ( zipWith
                  (+)
                  (take m (reverse r))
                  (take m (reverse i))
              )

fromComplexToQI :: Complex Int -> String
fromComplexToQI = shiftRight . fmap intToDigitQI . qizip

main :: IO ()
main =
  putStrLn (fromComplexToQI (35 :+ 23))
    >> print (fromQItoComplex "10.2" base)
Output:
121003.2
0.0 :+ 1.0

With base = 8i (you may choose any base):

3z.8
0.0 :+ 7.75

J

Implementation:

ibdec=: {{
  0j2 ibdec y
:
  digits=. 0,".,~&'36b'@> tolower y -.'. '
  (x #. digits) % x^#(}.~ 1+i.&'.')y-.' '
}}"1

ibenc=: {{
  0j2 ibenc y
:
  if.0=y do.,'0' return.end.
  sq=.*:x assert. 17 > sq
  step=. }.,~(1,|sq) +^:(0>{:@]) (0,sq) #: {.
  seq=. step^:(0~:{.)^:_"0
  're im0'=.+.y
  'im imf'=.(sign,1)*(0,|x)#:im0*sign=.*im0
  frac=. ,hfd (imf*|x)-.0 if.#frac do.frac=.'.',frac end.
  frac,~(}.~0 i.~_1}.'0'=]) }:,hfd|:0 1|."0 1 seq re,im
}}"0

This ibdec can decode numbers from complex bases up to 0j6, but this ibenc can only represent digits in complex bases up to 0j4.

Examples:

   (ibenc i:16),.' ',.ibenc j.i:16
1030000 2000    
1030001 2010.2  
1030002 2010    
1030003 2020.2  
300     2020    
301     2030.2  
302     2030    
303     1000.2  
200     1000    
201     1010.2  
202     1010    
203     1020.2  
100     1020    
101     1030.2  
102     1030    
103     0.2     
0       0       
1       0.2     
2       10      
3       10.2    
10300   20      
10301   20.2    
10302   30      
10303   30.2    
10200   103000  
10201   103000.2
10202   103010  
10203   103010.2
10100   103020  
10101   103020.2
10102   103030  
10103   103030.2
10000   102000  
   (ibdec ibenc i:16),:ibdec ibenc j.i:16
  _16   _15   _14   _13   _12   _11   _10   _9   _8   _7   _6   _5   _4   _3   _2   _1 0    1   2   3   4   5   6   7   8   9   10  11   12   13   14   15   16
0j_16 0j_15 0j_14 0j_13 0j_12 0j_11 0j_10 0j_9 0j_8 0j_7 0j_6 0j_5 0j_4 0j_3 0j_2 0j_1 0 0j_1 0j2 0j1 0j4 0j3 0j6 0j5 0j8 0j7 0j10 0j9 0j12 0j11 0j14 0j13 0j16
   0j4 ibenc 42
10e0a
   0j4 ibdec 0j4 ibenc 42
42

Java

Translation of: Kotlin
public class ImaginaryBaseNumber {
    private static class Complex {
        private Double real, imag;

        public Complex(double r, double i) {
            this.real = r;
            this.imag = i;
        }

        public Complex(int r, int i) {
            this.real = (double) r;
            this.imag = (double) i;
        }

        public Complex add(Complex rhs) {
            return new Complex(
                real + rhs.real,
                imag + rhs.imag
            );
        }

        public Complex times(Complex rhs) {
            return new Complex(
                real * rhs.real - imag * rhs.imag,
                real * rhs.imag + imag * rhs.real
            );
        }

        public Complex times(double rhs) {
            return new Complex(
                real * rhs,
                imag * rhs
            );
        }

        public Complex inv() {
            double denom = real * real + imag * imag;
            return new Complex(
                real / denom,
                -imag / denom
            );
        }

        public Complex unaryMinus() {
            return new Complex(-real, -imag);
        }

        public Complex divide(Complex rhs) {
            return this.times(rhs.inv());
        }

        // only works properly if 'real' and 'imag' are both integral
        public QuaterImaginary toQuaterImaginary() {
            if (real == 0.0 && imag == 0.0) return new QuaterImaginary("0");
            int re = real.intValue();
            int im = imag.intValue();
            int fi = -1;
            StringBuilder sb = new StringBuilder();
            while (re != 0) {
                int rem = re % -4;
                re /= -4;
                if (rem < 0) {
                    rem += 4;
                    re++;
                }
                sb.append(rem);
                sb.append(0);
            }
            if (im != 0) {
                Double f = new Complex(0.0, imag).divide(new Complex(0.0, 2.0)).real;
                im = ((Double) Math.ceil(f)).intValue();
                f = -4.0 * (f - im);
                int index = 1;
                while (im != 0) {
                    int rem = im % -4;
                    im /= -4;
                    if (rem < 0) {
                        rem += 4;
                        im++;
                    }
                    if (index < sb.length()) {
                        sb.setCharAt(index, (char) (rem + 48));
                    } else {
                        sb.append(0);
                        sb.append(rem);
                    }
                    index += 2;
                }
                fi = f.intValue();
            }
            sb.reverse();
            if (fi != -1) sb.append(".").append(fi);
            while (sb.charAt(0) == '0') sb.deleteCharAt(0);
            if (sb.charAt(0) == '.') sb.insert(0, '0');
            return new QuaterImaginary(sb.toString());
        }

        @Override
        public String toString() {
            double real2 = real == -0.0 ? 0.0 : real;  // get rid of negative zero
            double imag2 = imag == -0.0 ? 0.0 : imag;  // ditto
            String result = imag2 >= 0.0 ? String.format("%.0f + %.0fi", real2, imag2) : String.format("%.0f - %.0fi", real2, -imag2);
            result = result.replace(".0 ", " ").replace(".0i", "i").replace(" + 0i", "");
            if (result.startsWith("0 + ")) result = result.substring(4);
            if (result.startsWith("0 - ")) result = result.substring(4);
            return result;
        }
    }

    private static class QuaterImaginary {
        private static final Complex TWOI = new Complex(0.0, 2.0);
        private static final Complex INVTWOI = TWOI.inv();

        private String b2i;

        public QuaterImaginary(String b2i) {
            if (b2i.equals("") || !b2i.chars().allMatch(c -> "0123.".indexOf(c) > -1) || b2i.chars().filter(c -> c == '.').count() > 1) {
                throw new RuntimeException("Invalid Base 2i number");
            }
            this.b2i = b2i;
        }

        public Complex toComplex() {
            int pointPos = b2i.indexOf(".");
            int posLen = pointPos != -1 ? pointPos : b2i.length();
            Complex sum = new Complex(0, 0);
            Complex prod = new Complex(1, 0);

            for (int j = 0; j < posLen; ++j) {
                double k = b2i.charAt(posLen - 1 - j) - '0';
                if (k > 0.0) sum = sum.add(prod.times(k));
                prod = prod.times(TWOI);
            }
            if (pointPos != -1) {
                prod = INVTWOI;
                for (int j = posLen + 1; j < b2i.length(); ++j) {
                    double k = b2i.charAt(j) - '0';
                    if (k > 0.0) sum = sum.add(prod.times(k));
                    prod = prod.times(INVTWOI);
                }
            }

            return sum;
        }

        @Override
        public String toString() {
            return b2i;
        }
    }

    public static void main(String[] args) {
        String fmt = "%4s -> %8s -> %4s";
        for (int i = 1; i <= 16; ++i) {
            Complex c1 = new Complex(i, 0);
            QuaterImaginary qi = c1.toQuaterImaginary();
            Complex c2 = qi.toComplex();
            System.out.printf(fmt + "     ", c1, qi, c2);
            c1 = c2.unaryMinus();
            qi = c1.toQuaterImaginary();
            c2 = qi.toComplex();
            System.out.printf(fmt, c1, qi, c2);
            System.out.println();
        }
        System.out.println();
        for (int i = 1; i <= 16; ++i) {
            Complex c1 = new Complex(0, i);
            QuaterImaginary qi = c1.toQuaterImaginary();
            Complex c2 = qi.toComplex();
            System.out.printf(fmt + "     ", c1, qi, c2);
            c1 = c2.unaryMinus();
            qi = c1.toQuaterImaginary();
            c2 = qi.toComplex();
            System.out.printf(fmt, c1, qi, c2);
            System.out.println();
        }
    }
}
Output:
   1 ->        1 ->    1       -1 ->      103 ->   -1
   2 ->        2 ->    2       -2 ->      102 ->   -2
   3 ->        3 ->    3       -3 ->      101 ->   -3
   4 ->    10300 ->    4       -4 ->      100 ->   -4
   5 ->    10301 ->    5       -5 ->      203 ->   -5
   6 ->    10302 ->    6       -6 ->      202 ->   -6
   7 ->    10303 ->    7       -7 ->      201 ->   -7
   8 ->    10200 ->    8       -8 ->      200 ->   -8
   9 ->    10201 ->    9       -9 ->      303 ->   -9
  10 ->    10202 ->   10      -10 ->      302 ->  -10
  11 ->    10203 ->   11      -11 ->      301 ->  -11
  12 ->    10100 ->   12      -12 ->      300 ->  -12
  13 ->    10101 ->   13      -13 ->  1030003 ->  -13
  14 ->    10102 ->   14      -14 ->  1030002 ->  -14
  15 ->    10103 ->   15      -15 ->  1030001 ->  -15
  16 ->    10000 ->   16      -16 ->  1030000 ->  -16

  1i ->     10.2 ->   1i       1i ->      0.2 ->   1i
  2i ->     10.0 ->   2i       2i ->   1030.0 ->   2i
  3i ->     20.2 ->   3i       3i ->   1030.2 ->   3i
  4i ->     20.0 ->   4i       4i ->   1020.0 ->   4i
  5i ->     30.2 ->   5i       5i ->   1020.2 ->   5i
  6i ->     30.0 ->   6i       6i ->   1010.0 ->   6i
  7i -> 103000.2 ->   7i       7i ->   1010.2 ->   7i
  8i -> 103000.0 ->   8i       8i ->   1000.0 ->   8i
  9i -> 103010.2 ->   9i       9i ->   1000.2 ->   9i
 10i -> 103010.0 ->  10i      10i ->   2030.0 ->  10i
 11i -> 103020.2 ->  11i      11i ->   2030.2 ->  11i
 12i -> 103020.0 ->  12i      12i ->   2020.0 ->  12i
 13i -> 103030.2 ->  13i      13i ->   2020.2 ->  13i
 14i -> 103030.0 ->  14i      14i ->   2010.0 ->  14i
 15i -> 102000.2 ->  15i      15i ->   2010.2 ->  15i
 16i -> 102000.0 ->  16i      16i ->   2000.0 ->  16i

Julia

Translation of: C#
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()
Output:

        1 ->          1 ->            1               -1 ->        103 ->           -1
        2 ->          2 ->            2               -2 ->        102 ->           -2
        3 ->          3 ->            3               -3 ->        101 ->           -3
        4 ->      10300 ->            4               -4 ->        100 ->           -4
        5 ->      10301 ->            5               -5 ->        203 ->           -5
        6 ->      10302 ->            6               -6 ->        202 ->           -6
        7 ->      10303 ->            7               -7 ->        201 ->           -7
        8 ->      10200 ->            8               -8 ->        200 ->           -8
        9 ->      10201 ->            9               -9 ->        303 ->           -9
       10 ->      10202 ->           10              -10 ->        302 ->          -10
       11 ->      10203 ->           11              -11 ->        301 ->          -11
       12 ->      10100 ->           12              -12 ->        300 ->          -12
       13 ->      10101 ->           13              -13 ->    1030003 ->          -13
       14 ->      10102 ->           14              -14 ->    1030002 ->          -14
       15 ->      10103 ->           15              -15 ->    1030001 ->          -15
       16 ->      10000 ->           16              -16 ->    1030000 ->          -16
  0 + 1im ->       10.2 ->  0.0 + 1.0im          0 - 1im ->        0.2 ->  0.0 - 1.0im
  0 + 2im ->       10.0 ->  0.0 + 2.0im          0 - 2im ->     1030.0 ->  0.0 - 2.0im
  0 + 3im ->       20.2 ->  0.0 + 3.0im          0 - 3im ->     1030.2 ->  0.0 - 3.0im
  0 + 4im ->       20.0 ->  0.0 + 4.0im          0 - 4im ->     1020.0 ->  0.0 - 4.0im
  0 + 5im ->       30.2 ->  0.0 + 5.0im          0 - 5im ->     1020.2 ->  0.0 - 5.0im
  0 + 6im ->       30.0 ->  0.0 + 6.0im          0 - 6im ->     1010.0 ->  0.0 - 6.0im
  0 + 7im ->   103000.2 ->  0.0 + 7.0im          0 - 7im ->     1010.2 ->  0.0 - 7.0im
  0 + 8im ->   103000.0 ->  0.0 + 8.0im          0 - 8im ->     1000.0 ->  0.0 - 8.0im
  0 + 9im ->   103010.2 ->  0.0 + 9.0im          0 - 9im ->     1000.2 ->  0.0 - 9.0im
 0 + 10im ->   103010.0 -> 0.0 + 10.0im         0 - 10im ->     2030.0 -> 0.0 - 10.0im
 0 + 11im ->   103020.2 -> 0.0 + 11.0im         0 - 11im ->     2030.2 -> 0.0 - 11.0im
 0 + 12im ->   103020.0 -> 0.0 + 12.0im         0 - 12im ->     2020.0 -> 0.0 - 12.0im
 0 + 13im ->   103030.2 -> 0.0 + 13.0im         0 - 13im ->     2020.2 -> 0.0 - 13.0im
 0 + 14im ->   103030.0 -> 0.0 + 14.0im         0 - 14im ->     2010.0 -> 0.0 - 14.0im
 0 + 15im ->   102000.2 -> 0.0 + 15.0im         0 - 15im ->     2010.2 -> 0.0 - 15.0im
 0 + 16im ->   102000.0 -> 0.0 + 16.0im         0 - 16im ->     2000.0 -> 0.0 - 16.0im

Kotlin

The following deals with conversions to and from quater-imaginary only.

As the JDK lacks a complex number class, I've included a very basic one in the program.

// 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))
    }
}
Output:
   1 ->        1 ->    1       -1 ->      103 ->   -1
   2 ->        2 ->    2       -2 ->      102 ->   -2
   3 ->        3 ->    3       -3 ->      101 ->   -3
   4 ->    10300 ->    4       -4 ->      100 ->   -4
   5 ->    10301 ->    5       -5 ->      203 ->   -5
   6 ->    10302 ->    6       -6 ->      202 ->   -6
   7 ->    10303 ->    7       -7 ->      201 ->   -7
   8 ->    10200 ->    8       -8 ->      200 ->   -8
   9 ->    10201 ->    9       -9 ->      303 ->   -9
  10 ->    10202 ->   10      -10 ->      302 ->  -10
  11 ->    10203 ->   11      -11 ->      301 ->  -11
  12 ->    10100 ->   12      -12 ->      300 ->  -12
  13 ->    10101 ->   13      -13 ->  1030003 ->  -13
  14 ->    10102 ->   14      -14 ->  1030002 ->  -14
  15 ->    10103 ->   15      -15 ->  1030001 ->  -15
  16 ->    10000 ->   16      -16 ->  1030000 ->  -16

  1i ->     10.2 ->   1i      -1i ->      0.2 ->  -1i
  2i ->     10.0 ->   2i      -2i ->   1030.0 ->  -2i
  3i ->     20.2 ->   3i      -3i ->   1030.2 ->  -3i
  4i ->     20.0 ->   4i      -4i ->   1020.0 ->  -4i
  5i ->     30.2 ->   5i      -5i ->   1020.2 ->  -5i
  6i ->     30.0 ->   6i      -6i ->   1010.0 ->  -6i
  7i -> 103000.2 ->   7i      -7i ->   1010.2 ->  -7i
  8i -> 103000.0 ->   8i      -8i ->   1000.0 ->  -8i
  9i -> 103010.2 ->   9i      -9i ->   1000.2 ->  -9i
 10i -> 103010.0 ->  10i     -10i ->   2030.0 -> -10i
 11i -> 103020.2 ->  11i     -11i ->   2030.2 -> -11i
 12i -> 103020.0 ->  12i     -12i ->   2020.0 -> -12i
 13i -> 103030.2 ->  13i     -13i ->   2020.2 -> -13i
 14i -> 103030.0 ->  14i     -14i ->   2010.0 -> -14i
 15i -> 102000.2 ->  15i     -15i ->   2010.2 -> -15i
 16i -> 102000.0 ->  16i     -16i ->   2000.0 -> -16i

Modula-2

Translation of: C#
MODULE ImaginaryBase;
FROM FormatString IMPORT FormatString;
FROM RealMath IMPORT round;   
FROM Terminal IMPORT WriteString,WriteLn,ReadChar;

(* Helper *)
TYPE
    String = ARRAY[0..10] OF CHAR;
    StringBuilder = RECORD
        buf : String;
        ptr : CARDINAL;
    END;
    
PROCEDURE ToChar(n : INTEGER) : CHAR;
BEGIN
    CASE n OF
        0 : RETURN '0' |
        1 : RETURN '1' |
        2 : RETURN '2' |
        3 : RETURN '3' |
        4 : RETURN '4' |
        5 : RETURN '5' |
        6 : RETURN '6' |
        7 : RETURN '7' |
        8 : RETURN '8' |
        9 : RETURN '9'
    ELSE
        RETURN '-'
    END
END ToChar;

PROCEDURE AppendChar(VAR sb : StringBuilder; c : CHAR);
BEGIN        
    sb.buf[sb.ptr] := c;
    INC(sb.ptr);
    sb.buf[sb.ptr] := 0C
END AppendChar;

PROCEDURE AppendInt(VAR sb : StringBuilder; n : INTEGER);
BEGIN        
    sb.buf[sb.ptr] := ToChar(n);
    INC(sb.ptr);
    sb.buf[sb.ptr] := 0C
END AppendInt;

PROCEDURE Ceil(r : REAL) : REAL;
VAR t : REAL;
BEGIN       
    t := FLOAT(INT(r));    
    IF r - t > 0.0 THEN
        t := t + 1.0
    END;
    RETURN t
END Ceil;

PROCEDURE Modulus(q,d : INTEGER) : INTEGER;
VAR t : INTEGER;
BEGIN
    t := q / d;
    RETURN q - d * t
END Modulus;

PROCEDURE PrependInt(VAR sb : StringBuilder; n : INTEGER);
VAR i : CARDINAL;
BEGIN
    i := sb.ptr;
    INC(sb.ptr);
    sb.buf[sb.ptr] := 0C;
    WHILE i > 0 DO
        sb.buf[i] := sb.buf[i-1];
        DEC(i)
    END;       
    sb.buf[0] := ToChar(n)
END PrependInt;

PROCEDURE Reverse(VAR str : String);
VAR
    i,j : CARDINAL;
    c : CHAR;
BEGIN
    IF str[0] = 0C THEN RETURN END;
    i := 0;
    WHILE str[i] # 0C DO INC(i) END;
    DEC(i);
    j := 0;
    WHILE i > j DO
        c := str[i];
        str[i] := str[j];
        str[j] := c;
        
        DEC(i);
        INC(j)
    END
END Reverse;

PROCEDURE TrimStart(VAR str : String; c : CHAR);
VAR i : CARDINAL;
BEGIN            
    WHILE str[0] = c DO
        i := 0;
        WHILE str[i] # 0C DO
            str[i] := str[i+1];
            INC(i)
        END
    END
END TrimStart;

PROCEDURE WriteInteger(n : INTEGER);
VAR buf : ARRAY[0..15] OF CHAR;
BEGIN
    FormatString("%i", buf, n);
    WriteString(buf)
END WriteInteger;

(* Imaginary *)
TYPE
    Complex = RECORD
        real,imag : REAL; 
    END;
    QuaterImaginary = RECORD
        b2i : String;
    END;
    
PROCEDURE ComplexMul(lhs,rhs : Complex) : Complex;
BEGIN         
    RETURN Complex{
        rhs.real * lhs.real - rhs.imag * lhs.imag,
        rhs.real * lhs.imag + rhs.imag * lhs.real
    }                            
END ComplexMul;
    
PROCEDURE ComplexMulR(lhs : Complex; rhs : REAL) : Complex;
BEGIN         
    RETURN Complex{lhs.real * rhs, lhs.imag * rhs}                            
END ComplexMulR;

PROCEDURE ComplexInv(c : Complex) : Complex;
VAR denom : REAL;
BEGIN
    denom := c.real * c.real + c.imag * c.imag;
    RETURN Complex{c.real / denom, -c.imag / denom}
END ComplexInv;
    
PROCEDURE ComplexDiv(lhs,rhs : Complex) : Complex;
BEGIN                                     
    RETURN ComplexMul(lhs, ComplexInv(rhs))
END ComplexDiv;

PROCEDURE ComplexNeg(c : Complex) : Complex;
BEGIN
    RETURN Complex{-c.real, -c.imag}
END ComplexNeg;
    
PROCEDURE ComplexSum(lhs,rhs : Complex) : Complex;
BEGIN                                     
    RETURN Complex{lhs.real + rhs.real, lhs.imag + rhs.imag}
END ComplexSum;

PROCEDURE WriteComplex(c : Complex);
VAR buf : ARRAY[0..15] OF CHAR;
BEGIN                    
    IF c.imag = 0.0 THEN      
        WriteInteger(INT(c.real))
    ELSIF c.real = 0.0 THEN
        WriteInteger(INT(c.imag));
        WriteString("i")
    ELSIF c.imag > 0.0 THEN
        WriteInteger(INT(c.real));
        WriteString(" + ");
        WriteInteger(INT(c.imag));
        WriteString("i")
    ELSE
        WriteInteger(INT(c.real));
        WriteString(" - ");
        WriteInteger(INT(-c.imag));
        WriteString("i")
    END
END WriteComplex;
    
PROCEDURE ToQuaterImaginary(c : Complex) : QuaterImaginary;
VAR
    re,im,fi,rem,index : INTEGER;
    f : REAL;
    t : Complex;
    sb : StringBuilder;
BEGIN
    IF (c.real = 0.0) AND (c.imag = 0.0) THEN RETURN QuaterImaginary{"0"} END;
    re := INT(c.real);
    im := INT(c.imag);
    fi := -1;
    sb := StringBuilder{"", 0};    
    WHILE re # 0 DO
        rem := Modulus(re, -4);
        re := re / (-4);
        IF rem < 0 THEN
            rem := 4 + rem;
            INC(re)
        END;
        AppendInt(sb, rem);
        AppendInt(sb, 0)
    END;
    IF im # 0 THEN
        t := ComplexDiv(Complex{0.0, c.imag}, Complex{0.0, 2.0});
        f := t.real;
        im := INT(Ceil(f));
        f := -4.0 * (f - FLOAT(im));
        index := 1;
        WHILE im # 0 DO
            rem := Modulus(im, -4);
            im := im / (-4);
            IF rem < 0 THEN
                rem := 4 + rem;
                INC(im)
            END;
            IF index < INT(sb.ptr) THEN
                sb.buf[index] := ToChar(rem)
            ELSE
                AppendInt(sb, 0);
                AppendInt(sb, rem)
            END;
            index := index + 2;
        END;
        fi := INT(f)
    END;
    Reverse(sb.buf);
    IF fi # -1 THEN
        AppendChar(sb, '.');
        AppendInt(sb, fi)
    END;
    TrimStart(sb.buf, '0');
    IF sb.buf[0] = '.' THEN
        PrependInt(sb, 0)
    END;
    
    RETURN QuaterImaginary{sb.buf}
END ToQuaterImaginary;

PROCEDURE ToComplex(qi : QuaterImaginary) : Complex;
VAR
    j,pointPos,posLen,b2iLen : INTEGER;
    k : REAL;
    sum,prod : Complex;
BEGIN       
    pointPos := 0;
    WHILE (qi.b2i[pointPos] # 0C) AND (qi.b2i[pointPos] # '.') DO
        INC(pointPos)
    END;            
    IF qi.b2i[pointPos] # '.' THEN
        pointPos := -1;
        posLen := 0;
        WHILE qi.b2i[posLen] # 0C DO
            INC(posLen)
        END
    ELSE
        posLen := pointPos
    END;

    sum := Complex{0.0, 0.0};
    prod := Complex{1.0, 0.0};
    
    FOR j:=0 TO posLen - 1 DO
        k := FLOAT(ORD(qi.b2i[posLen - 1 - j]) - ORD('0'));
        IF k > 0.0 THEN
            sum := ComplexSum(sum, ComplexMulR(prod, k))
        END;
        prod := ComplexMul(prod, Complex{0.0, 2.0})
    END;
    
    IF pointPos # -1 THEN
        prod := ComplexInv(Complex{0.0, 2.0});
        b2iLen := 0;
        WHILE qi.b2i[b2iLen] # 0C DO INC(b2iLen) END;
        FOR j:=posLen + 1 TO b2iLen - 1 DO
            k := FLOAT(ORD(qi.b2i[j]) - ORD('0'));
            IF k > 0.0 THEN
                sum := ComplexSum(sum, ComplexMulR(prod, k))
            END;
            prod := ComplexMul(prod, ComplexInv(Complex{0.0, 2.0}))
        END
    END;
    
    RETURN sum
END ToComplex;

(* Main *)
VAR 
    c1,c2 : Complex;
    qi : QuaterImaginary;
    i : INTEGER;
BEGIN
    FOR i:=1 TO 16 DO
        c1 := Complex{FLOAT(i), 0.0};
        WriteComplex(c1);
        WriteString(" -> ");
        qi := ToQuaterImaginary(c1);
        WriteString(qi.b2i);
        WriteString(" -> ");
        c2 := ToComplex(qi);
        WriteComplex(c2);
        WriteString("   ");
        
        c1 := ComplexNeg(c1);
        WriteComplex(c1);
        WriteString(" -> ");
        qi := ToQuaterImaginary(c1);
        WriteString(qi.b2i);
        WriteString(" -> ");
        c2 := ToComplex(qi);
        WriteComplex(c2);
        WriteLn
    END;
    WriteLn;

    FOR i:=1 TO 16 DO
        c1 := Complex{0.0, FLOAT(i)};
        WriteComplex(c1);
        WriteString(" -> ");
        qi := ToQuaterImaginary(c1);
        WriteString(qi.b2i);
        WriteString(" -> ");
        c2 := ToComplex(qi);
        WriteComplex(c2);
        WriteString("   ");

        c1 := ComplexNeg(c1);
        WriteComplex(c1);
        WriteString(" -> ");
        qi := ToQuaterImaginary(c1);
        WriteString(qi.b2i);
        WriteString(" -> ");
        c2 := ToComplex(qi);
        WriteComplex(c2);
        WriteLn
    END;

    ReadChar
END ImaginaryBase.
Output:
1 -> 1 -> 1   -1 -> 103 -> -1
2 -> 2 -> 2   -2 -> 102 -> -2
3 -> 3 -> 3   -3 -> 101 -> -3
4 -> 10300 -> 4   -4 -> 100 -> -4
5 -> 10301 -> 5   -5 -> 203 -> -5
6 -> 10302 -> 6   -6 -> 202 -> -6
7 -> 10303 -> 7   -7 -> 201 -> -7
8 -> 10200 -> 8   -8 -> 200 -> -8
9 -> 10201 -> 9   -9 -> 303 -> -9
10 -> 10202 -> 10   -10 -> 302 -> -10
11 -> 10203 -> 11   -11 -> 301 -> -11
12 -> 10100 -> 12   -12 -> 300 -> -12
13 -> 10101 -> 13   -13 -> 1030003 -> -13
14 -> 10102 -> 14   -14 -> 1030002 -> -14
15 -> 10103 -> 15   -15 -> 1030001 -> -15
16 -> 10000 -> 16   -16 -> 1030000 -> -16

1i -> 10.2 -> 1i   -1i -> 0.2 -> -1i
2i -> 10.0 -> 2i   -2i -> 1030.0 -> -2i
3i -> 20.2 -> 3i   -3i -> 1030.2 -> -3i
4i -> 20.0 -> 4i   -4i -> 1020.0 -> -4i
5i -> 30.2 -> 5i   -5i -> 1020.2 -> -5i
6i -> 30.0 -> 6i   -6i -> 1010.0 -> -6i
7i -> 103000.2 -> 7i   -7i -> 1010.2 -> -7i
8i -> 103000.0 -> 8i   -8i -> 1000.0 -> -8i
9i -> 103010.2 -> 9i   -9i -> 1000.2 -> -9i
10i -> 103010.0 -> 10i   -10i -> 2030.0 -> -10i
11i -> 103020.2 -> 11i   -11i -> 2030.2 -> -11i
12i -> 103020.0 -> 12i   -12i -> 2020.0 -> -12i
13i -> 103030.2 -> 13i   -13i -> 2020.2 -> -13i
14i -> 103030.0 -> 14i   -14i -> 2010.0 -> -14i
15i -> 102000.2 -> 15i   -15i -> 2010.2 -> -15i
16i -> 102000.0 -> 16i   -16i -> 2000.0 -> -16i

Nim

Translation of: Kotlin

This is a fairly faithful translation of the Kotlin program except that we had not to define a Complex type as Nim provides the module “complex” in its standard library. We had only to define a function “toString” for the “Complex[float]” type, function to use in place of “$” in order to get a more pleasant output.

import algorithm, complex, math, strformat, strutils

const
  TwoI = complex(0.0, 2.0)
  InvTwoI = inv(TwoI)

type QuaterImaginery = object
  b2i: string

# Conversions between digit character and digit value.
template digitChar(n: range[0..9]): range['0'..'9'] = chr(n + ord('0'))
template digitValue(c: range['0'..'9']): range[0..9] = ord(c) - ord('0')


####################################################################################################
# Quater imaginary functions.

func initQuaterImaginary(s: string): QuaterImaginery =
  ## Create a Quater imaginary number.
  if s.len == 0 or not s.allCharsInSet({'0'..'3', '.'}) or s.count('.') > 1:
    raise newException(ValueError, "invalid base 2i number.")
  result = QuaterImaginery(b2i: s)

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

func toComplex(q: QuaterImaginery): Complex[float] =
  ## Convert a Quater imaginary number to a complex.

  let pointPos = q.b2i.find('.')
  let posLen = if pointPos != -1: pointPos else: q.b2i.len
  var prod = complex(1.0)

  for j in 0..<posLen:
    let k = float(q.b2i[posLen - 1 - j].digitValue)
    if k > 0: result += prod * k
    prod *= TwoI

  if pointPos != -1:
    prod = InvTwoI
    for j in (posLen + 1)..q.b2i.high:
      let k = float(q.b2i[j].digitValue)
      if k > 0: result += prod * k
      prod *= InvTwoI

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

func `$`(q: QuaterImaginery): string =
  ## Convert a Quater imaginary number to a string.
  q.b2i


####################################################################################################
# Supplementary functions for complex numbers.

func toQuaterImaginary(c: Complex): QuaterImaginery =
  ## Convert a complex number to a Quater imaginary number.

  if c.re == 0 and c.im == 0: return initQuaterImaginary("0")

  var re = c.re.toInt
  var im = c.im.toInt
  var fi = -1

  while re != 0:
    var rem = re mod -4
    re = re div -4
    if rem < 0:
      inc rem, 4
      inc re
    result.b2i.add rem.digitChar
    result.b2i.add '0'

  if im != 0:
    var f = (complex(0.0, c.im) / TwoI).re
    im = f.ceil.toInt
    f = -4 * (f - im.toFloat)
    var index = 1
    while im != 0:
      var rem = im mod -4
      im = im div -4
      if rem < 0:
        inc rem, 4
        inc im
      if index < result.b2i.len:
        result.b2i[index] = rem.digitChar
      else:
        result.b2i.add '0'
        result.b2i.add rem.digitChar
      inc index, 2
    fi = f.toInt

  result.b2i.reverse()
  if fi != -1: result.b2i.add "." & $fi
  result.b2i = result.b2i.strip(leading = true, trailing = false, {'0'})
  if result.b2i.startsWith('.'): result.b2i = '0' & result.b2i

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

func toString(c: Complex[float]): string =
  ## Convert a complex number to a string.
  ## This function is used in place of `$`.

  let real = if c.re.classify == fcNegZero: 0.0 else: c.re
  let imag = if c.im.classify == fcNegZero: 0.0 else: c.im
  result = if imag >= 0: fmt"{real} + {imag}i" else: fmt"{real} - {-imag}i"
  result = result.replace(".0 ", " ").replace(".0i", "i").replace(" + 0i", "")
  if result.startsWith("0 + "): result = result[4..^1]
  if result.startsWith("0 - "): result = '-' & result[4..^1]


#———————————————————————————————————————————————————————————————————————————————————————————————————

when isMainModule:

  for i in 1..16:
    var c1 = complex(i.toFloat)
    var qi = c1.toQuaterImaginary
    var c2 = qi.toComplex
    stdout.write fmt"{c1.toString:>4s} → {qi:>8s} → {c2.toString:>4s}     "
    c1 = -c1
    qi = c1.toQuaterImaginary
    c2 = qi.toComplex
    echo fmt"{c1.toString:>4s} → {qi:>8s} → {c2.toString:>4s}"

  echo ""

  for i in 1..16:
    var c1 = complex(0.0, i.toFloat)
    var qi = c1.toQuaterImaginary
    var c2 = qi.toComplex
    stdout.write fmt"{c1.toString:>4s} → {qi:>8s} → {c2.toString:>4s}     "
    c1 = -c1
    qi = c1.toQuaterImaginary
    c2 = qi.toComplex
    echo fmt"{c1.toString:>4s} → {qi:>8s} → {c2.toString:>4s}"
Output:
   1 →        1 →    1       -1 →      103 →   -1
   2 →        2 →    2       -2 →      102 →   -2
   3 →        3 →    3       -3 →      101 →   -3
   4 →    10300 →    4       -4 →      100 →   -4
   5 →    10301 →    5       -5 →      203 →   -5
   6 →    10302 →    6       -6 →      202 →   -6
   7 →    10303 →    7       -7 →      201 →   -7
   8 →    10200 →    8       -8 →      200 →   -8
   9 →    10201 →    9       -9 →      303 →   -9
  10 →    10202 →   10      -10 →      302 →  -10
  11 →    10203 →   11      -11 →      301 →  -11
  12 →    10100 →   12      -12 →      300 →  -12
  13 →    10101 →   13      -13 →  1030003 →  -13
  14 →    10102 →   14      -14 →  1030002 →  -14
  15 →    10103 →   15      -15 →  1030001 →  -15
  16 →    10000 →   16      -16 →  1030000 →  -16

  1i →     10.2 →   1i      -1i →      0.2 →  -1i
  2i →     10.0 →   2i      -2i →   1030.0 →  -2i
  3i →     20.2 →   3i      -3i →   1030.2 →  -3i
  4i →     20.0 →   4i      -4i →   1020.0 →  -4i
  5i →     30.2 →   5i      -5i →   1020.2 →  -5i
  6i →     30.0 →   6i      -6i →   1010.0 →  -6i
  7i → 103000.2 →   7i      -7i →   1010.2 →  -7i
  8i → 103000.0 →   8i      -8i →   1000.0 →  -8i
  9i → 103010.2 →   9i      -9i →   1000.2 →  -9i
 10i → 103010.0 →  10i     -10i →   2030.0 → -10i
 11i → 103020.2 →  11i     -11i →   2030.2 → -11i
 12i → 103020.0 →  12i     -12i →   2020.0 → -12i
 13i → 103030.2 →  13i     -13i →   2020.2 → -13i
 14i → 103030.0 →  14i     -14i →   2010.0 → -14i
 15i → 102000.2 →  15i     -15i →   2010.2 → -15i
 16i → 102000.0 →  16i     -16i →   2000.0 → -16i

Perl

Translation of: Raku
Library: ntheory
use strict;
use warnings;
use feature 'say';

use Math::Complex;
use List::AllUtils qw(sum mesh);
use ntheory qw<todigitstring fromdigits>;

sub zip {
    my($a,$b) = @_;
    my($la, $lb) = (length $a, length $b);
    my $l = '0' x abs $la - $lb;
    $a .= $l if $la < $lb;
    $b .= $l if $lb < $la;
    (join('', mesh(@{[split('',$a),]}, @{[split('',$b),]})) =~ s/0+$//r) or 0;
}

sub base_i {
    my($num,$radix,$precision) = @_;
    die unless $radix > -37 and $radix < -1;
    return '0' unless $num;
    my $value  = $num;
    my $result = '';
    my $place  = 0;
    my $upper_bound = 1 / (-$radix + 1);
    my $lower_bound = $radix * $upper_bound;

    $value = $num / $radix ** ++$place until $lower_bound <= $value and $value < $upper_bound;

    while (($value or $place > 0) and $place > $precision) {
        my $digit = int $radix * $value - $lower_bound;
        $value    =  $radix * $value - $digit;
        $result  .= '.' unless $place or not index($result, '.');
        $result  .= $digit == -$radix ? todigitstring($digit-1, -$radix) . '0' : (todigitstring($digit, -$radix) or '0');
        $place--;
    }
    $result
}

sub base_c {
    my($num, $radix, $precision) = @_;
    die "Base $radix out of range" unless
        (-6 <= $radix->Im or $radix->Im <= -2) or (2 <= $radix->Im or $radix->Im <= 6);
    my ($re,$im);
    defined $num->Im ? ($re, $im) = ($num->Re, $num->Im) : $re = $num;
    my ($re_wh, $re_fr) = split /\./, base_i(  $re,               -1 * int($radix->Im**2), $precision);
    my ($im_wh, $im_fr) = split /\./, base_i( ($im/($radix->Im)), -1 * int($radix->Im**2), $precision);
    $_ //= '' for $re_fr, $im_fr;

    my $whole = reverse zip scalar reverse($re_wh), scalar reverse($im_wh);
    my $fraction = zip $im_fr, $re_fr;
    $fraction eq 0 ? "$whole" : "$whole.$fraction"
}

sub parse_base {
    my($str, $radix) = @_;
    return -1 * parse_base( substr($str,1), $radix) if substr($str,0,1) eq '-';
    my($whole, $frac) = split /\./, $str;
    my $fraction = 0;
    my $k = 0;
    $fraction = sum map { (fromdigits($_, int $radix->Im**2) || 0) * $radix ** -($k++ +1) } split '', $frac
        if $frac;
    $k = 0;
    $fraction + sum map { (fromdigits($_, int $radix->Im**2) || 0) * $radix ** $k++  } reverse split '', $whole;
}

for (
    [  0*i,  2*i], [1+0*i,  2*i], [5+0*i,      2*i], [ -13+0*i, 2*i],
    [  9*i,  2*i], [ -3*i,  2*i], [7.75-7.5*i, 2*i], [0.25+0*i, 2*i],
    [5+5*i,  2*i], [5+5*i,  3*i], [5+5*i,  4*i], [5+5*i,  5*i], [5+5*i,  6*i],
    [5+5*i, -2*i], [5+5*i, -3*i], [5+5*i, -4*i], [5+5*i, -5*i], [5+5*i, -6*i]
) {
    my($v,$r) = @$_;
    my $ibase = base_c($v, $r, -6);
    my $rt = cplx parse_base($ibase, $r);
    $rt->display_format('format' => '%.2f');
    printf "base(%3s): %10s  => %9s  => %13s\n", $r, $v, $ibase, $rt;
}

say '';
say 'base( 6i): 31432.6219135802-2898.5266203704*i => ' .
         base_c(31432.6219135802-2898.5266203704*i, 0+6*i, -3);
Output:
base( 2i):          0  =>         0  =>             0
base( 2i):          1  =>         1  =>          1.00
base( 2i):          5  =>     10301  =>    5.00-0.00i
base( 2i):        -13  =>   1030003  =>  -13.00+0.00i
base( 2i):         9i  =>  103010.2  =>    0.00+9.00i
base( 2i):        -3i  =>    1030.2  =>   -0.00-3.00i
base( 2i):  7.75-7.5i  =>  11210.31  =>    7.75-7.50i
base( 2i):       0.25  =>      1.03  =>    0.25-0.00i
base( 2i):       5+5i  =>   10331.2  =>    5.00+5.00i
base( 3i):       5+5i  =>      25.3  =>    5.00+5.00i
base( 4i):       5+5i  =>      25.c  =>    5.00+5.00i
base( 5i):       5+5i  =>        15  =>    5.00+5.00i
base( 6i):       5+5i  =>      15.6  =>    5.00+5.00i
base(-2i):       5+5i  =>   11321.2  =>    5.00+5.00i
base(-3i):       5+5i  =>    1085.6  =>    5.00+5.00i
base(-4i):       5+5i  =>    10f5.4  =>    5.00+5.00i
base(-5i):       5+5i  =>      10o5  =>    5.00+5.00i
base(-6i):       5+5i  =>       5.u  =>    5.00+5.00i

base( 6i): 31432.6219135802-2898.5266203704*i => perl5.4ever

Phix

Translation of: Sidef
with javascript_semantics
include complex.e
 
function base2(atom num, integer radix, precision = -8)
    if radix<-36 or radix>-2 then throw("radix out of range (-2..-36)") end if
    sequence result
    if num=0 then
        result = {"0",""}
    else
        integer place = 0
        result = ""
        atom v = num
        atom upper_bound = 1/(1-radix),
             lower_bound = radix*upper_bound
        while not(lower_bound <= v) or not(v < upper_bound) do
            place += 1
            v = num/power(radix,place)
        end while
 
        while (v or place > 0) and (place > precision) do
            integer digit = floor(radix*v - lower_bound)
            v = (radix*v - digit)
            if place=0 and not find('.',result) then result &= '.' end if
            result &= digit+iff(digit>9?'a'-10:'0')
            place -= 1
        end while
        integer dot = find('.',result)
        if dot then
            result = trim_tail(result,'0')
            result = {result[1..dot-1],result[dot+1..$]}
        else
            result = {result,""}
        end if
    end if
    return result
end function
 
function zip(string a, string b)
    integer ld = length(a)-length(b)
    if ld!=0 then
        if ld>0 then
            b &= repeat('0',ld)
        else
            a &= repeat('0',abs(ld))
        end if
    end if
    string res = ""
    for i=1 to length(a) do
        res &= a[i]&b[i]
    end for
    res = trim_tail(res,'0')
    if res="" then res = "0" end if
    return res
end function
 
function base(complexn num, integer radix, precision = -8)
 
    integer absrad = abs(radix),
            radix2 = -power(radix,2)
    if absrad<2 or absrad>6 then throw("base radix out of range") end if
 
    atom {re, im}         = {complex_real(num), complex_imag(num)}
    string {re_wh, re_fr} = base2(re,       radix2, precision),
           {im_wh, im_fr} = base2(im/radix, radix2, precision)
 
    string whole = reverse(zip(reverse(re_wh), reverse(im_wh))),
           fraction = zip(im_fr, re_fr)
    if fraction!="0" then whole &= '.'&fraction end if
    return whole
end function
 
function parse_base(string str, integer radix)
 
    complexn fraction = 0
 
    integer dot = find('.',str)
    if dot then
        string fr = str[dot+1..$]
        for i=1 to length(fr) do
            integer c = fr[i]
            c -= iff(c>='a'?'a'-10:'0')
            fraction = complex_add(fraction,complex_mul(c,complex_power({0,radix},-i)))
        end for
        str = str[1..dot-1]
    end if
 
    str = reverse(str)
    for i=1 to length(str) do
        integer c = str[i]
        c -= iff(c>='a'?'a'-10:'0')
        fraction = complex_add(fraction,complex_mul(c,complex_power({0,radix},(i-1))))
    end for
 
    return fraction
end function
 
constant tests = {{0,2},{1,2},{5,2},{-13,2},{{0,9},2},{{0,-3},2},{{7.75,-7.5}, 2},{.25, 2}, -- base 2i tests
                  {{5,5}, 2},{{5,5}, 3},{{5,5}, 4},{{5,5}, 5},{{5,5}, 6}, -- same value, positive imaginary bases
                  {{5,5},-2},{{5,5},-3},{{5,5},-4},{{5,5},-5},{{5,5},-6}, -- same value, negative imaginary bases
                  {{227.65625,10.859375},4}, -- larger test value
                  {{-579.8225308641975744,-5296.406378600824},6}}   -- phix.rules
 
-- matches output of Sidef and Raku:
for t=1 to length(tests) do
    {complexn v, integer r} = tests[t]
    string ibase = base(v,r),
           strv = complex_sprint(v),
           strb = complex_sprint(parse_base(ibase, r))
    printf(1,"base(%20s, %2di) = %-10s : parse_base(%12s, %2di) = %s\n",
                  {strv,  r,     ibase,    '"'&ibase&'"', r,     strb})
end for
 
-- matches output of Kotlin, Java, Go, D, and C#:
for ri=1 to 2 do    -- real then imag
    for i=1 to 16 do
        complexn c = iff(ri=1?i:{0,i}),
                nc = complex_neg(c)
        string sc = complex_sprint(c),
              snc = complex_sprint(nc),
               ib = base(c,2),
              inb = base(nc,2),
               rc = complex_sprint(parse_base(ib,2)),
              rnc = complex_sprint(parse_base(inb,2))
        printf(1,"%4s -> %8s -> %4s     %4s -> %8s -> %4s\n",
                 {sc,    ib,    rc,     snc,   inb,   rnc })
    end for
    puts(1,"\n")
end for
Output:

Matches the output of Sidef and Raku, except for the final line:

base(   -579.823-5296.41i,  6i) = phix.rules : parse_base("phix.rules",  6i) = -579.823-5296.41i

Also matches the output of Kotlin, Java, Go, D, and C#, except the even entries in the second half, eg:

  2i ->       10 ->   2i      -2i ->     1030 ->  -2i

instead of

  2i ->     10.0 ->   2i      -2i ->   1030.0 ->  -2i

ie the unnecessary trailing ".0" are trimmed. (see talk page)

Python

Translation of: C++
import math
import re

def inv(c):
    denom = c.real * c.real + c.imag * c.imag
    return complex(c.real / denom, -c.imag / denom)

class QuaterImaginary:
    twoI = complex(0, 2)
    invTwoI = inv(twoI)

    def __init__(self, str):
        if not re.match("^[0123.]+$", str) or str.count('.') > 1:
            raise Exception('Invalid base 2i number')
        self.b2i = str

    def toComplex(self):
        pointPos = self.b2i.find('.')
        posLen = len(self.b2i) if (pointPos < 0) else pointPos
        sum = complex(0, 0)
        prod = complex(1, 0)
        for j in xrange(0, posLen):
            k = int(self.b2i[posLen - 1 - j])
            if k > 0:
                sum += prod * k
            prod *= QuaterImaginary.twoI
        if pointPos != -1:
            prod = QuaterImaginary.invTwoI
            for j in xrange(posLen + 1, len(self.b2i)):
                k = int(self.b2i[j])
                if k > 0:
                    sum += prod * k
                prod *= QuaterImaginary.invTwoI
        return sum

    def __str__(self):
        return str(self.b2i)

def toQuaterImaginary(c):
    if c.real == 0.0 and c.imag == 0.0:
        return QuaterImaginary("0")

    re = int(c.real)
    im = int(c.imag)
    fi = -1
    ss = ""
    while re != 0:
        re, rem = divmod(re, -4)
        if rem < 0:
            rem += 4
            re += 1
        ss += str(rem) + '0'
    if im != 0:
        f = c.imag / 2
        im = int(math.ceil(f))
        f = -4 * (f - im)
        index = 1
        while im != 0:
            im, rem = divmod(im, -4)
            if rem < 0:
                rem += 4
                im += 1
            if index < len(ss):
                ss[index] = str(rem)
            else:
                ss += '0' + str(rem)
            index = index + 2
        fi = int(f)
    ss = ss[::-1]
    if fi != -1:
        ss += '.' + str(fi)
    ss = ss.lstrip('0')
    if ss[0] == '.':
        ss = '0' + ss
    return QuaterImaginary(ss)

for i in xrange(1,17):
    c1 = complex(i, 0)
    qi = toQuaterImaginary(c1)
    c2 = qi.toComplex()
    print "{0:8} -> {1:>8} -> {2:8}     ".format(c1, qi, c2),

    c1 = -c1
    qi = toQuaterImaginary(c1)
    c2 = qi.toComplex()
    print "{0:8} -> {1:>8} -> {2:8}".format(c1, qi, c2)
print

for i in xrange(1,17):
    c1 = complex(0, i)
    qi = toQuaterImaginary(c1)
    c2 = qi.toComplex()
    print "{0:8} -> {1:>8} -> {2:8}     ".format(c1, qi, c2),

    c1 = -c1
    qi = toQuaterImaginary(c1)
    c2 = qi.toComplex()
    print "{0:8} -> {1:>8} -> {2:8}".format(c1, qi, c2)

print "done"
Output:
  (1+0j) ->        1 ->   (1+0j)       (-1-0j) ->      103 ->  (-1+0j)
  (2+0j) ->        2 ->   (2+0j)       (-2-0j) ->      102 ->  (-2+0j)
  (3+0j) ->        3 ->   (3+0j)       (-3-0j) ->      101 ->  (-3+0j)
  (4+0j) ->    10300 ->   (4+0j)       (-4-0j) ->      100 ->  (-4+0j)
  (5+0j) ->    10301 ->   (5+0j)       (-5-0j) ->      203 ->  (-5+0j)
  (6+0j) ->    10302 ->   (6+0j)       (-6-0j) ->      202 ->  (-6+0j)
  (7+0j) ->    10303 ->   (7+0j)       (-7-0j) ->      201 ->  (-7+0j)
  (8+0j) ->    10200 ->   (8+0j)       (-8-0j) ->      200 ->  (-8+0j)
  (9+0j) ->    10201 ->   (9+0j)       (-9-0j) ->      303 ->  (-9+0j)
 (10+0j) ->    10202 ->  (10+0j)      (-10-0j) ->      302 -> (-10+0j)
 (11+0j) ->    10203 ->  (11+0j)      (-11-0j) ->      301 -> (-11+0j)
 (12+0j) ->    10100 ->  (12+0j)      (-12-0j) ->      300 -> (-12+0j)
 (13+0j) ->    10101 ->  (13+0j)      (-13-0j) ->  1030003 -> (-13+0j)
 (14+0j) ->    10102 ->  (14+0j)      (-14-0j) ->  1030002 -> (-14+0j)
 (15+0j) ->    10103 ->  (15+0j)      (-15-0j) ->  1030001 -> (-15+0j)
 (16+0j) ->    10000 ->  (16+0j)      (-16-0j) ->  1030000 -> (-16+0j)

      1j ->     10.2 ->       1j       (-0-1j) ->      0.2 ->      -1j
      2j ->     10.0 ->       2j       (-0-2j) ->   1030.0 ->      -2j
      3j ->     20.2 ->       3j       (-0-3j) ->   1030.2 ->      -3j
      4j ->     20.0 ->       4j       (-0-4j) ->   1020.0 ->      -4j
      5j ->     30.2 ->       5j       (-0-5j) ->   1020.2 ->      -5j
      6j ->     30.0 ->       6j       (-0-6j) ->   1010.0 ->      -6j
      7j -> 103000.2 ->       7j       (-0-7j) ->   1010.2 ->      -7j
      8j -> 103000.0 ->       8j       (-0-8j) ->   1000.0 ->      -8j
      9j -> 103010.2 ->       9j       (-0-9j) ->   1000.2 ->      -9j
     10j -> 103010.0 ->      10j      (-0-10j) ->   2030.0 ->     -10j
     11j -> 103020.2 ->      11j      (-0-11j) ->   2030.2 ->     -11j
     12j -> 103020.0 ->      12j      (-0-12j) ->   2020.0 ->     -12j
     13j -> 103030.2 ->      13j      (-0-13j) ->   2020.2 ->     -13j
     14j -> 103030.0 ->      14j      (-0-14j) ->   2010.0 ->     -14j
     15j -> 102000.2 ->      15j      (-0-15j) ->   2010.2 ->     -15j
     16j -> 102000.0 ->      16j      (-0-16j) ->   2000.0 ->     -16j
done

Raku

(formerly Perl 6)

Explicit

Works with: Rakudo version 2017.01

These are generalized imaginary-base conversion routines. They only work for imaginary bases, not complex. (Any real portion of the radix must be zero.) Theoretically they could be made to work for any imaginary base; in practice, they are limited to integer bases from -6i to -2i and 2i to 6i. Bases -1i and 1i exist but require special handling and are not supported. Bases larger than 6i (or -6i) require digits outside of base 36 to express them, so aren't as standardized, are implementation dependent and are not supported. Note that imaginary number coefficients are stored as floating point numbers in Raku so some rounding error may creep in during calculations. The precision these conversion routines use is configurable; we are using 6 decimal, um... radicimal(?) places of precision here.

Implements minimum, extra kudos and stretch goals.

multi sub base ( Real $num, Int $radix where -37 < * < -1, :$precision = -15 ) {
    return '0' unless $num;
    my $value  = $num;
    my $result = '';
    my $place  = 0;
    my $upper-bound = 1 / (-$radix + 1);
    my $lower-bound = $radix * $upper-bound;

    $value = $num / $radix ** ++$place until $lower-bound <= $value < $upper-bound;

    while ($value or $place > 0) and $place > $precision {
        my $digit = ($radix * $value - $lower-bound).Int;
        $value    =  $radix * $value - $digit;
        $result ~= '.' unless $place or $result.contains: '.';
        $result ~= $digit == -$radix ?? ($digit-1).base(-$radix)~'0' !! $digit.base(-$radix);
        $place--
    }
    $result
}

multi sub base (Numeric $num, Complex $radix where *.re == 0, :$precision = -8 ) {
    die "Base $radix out of range" unless -6 <= $radix.im <= -2 or 2 <= $radix.im <= 6;
    my ($re, $im) = $num.Complex.reals;
    my ($re-wh, $re-fr) =             $re.&base( -$radix.im².Int, :precision($precision) ).split: '.';
    my ($im-wh, $im-fr) = ($im/$radix.im).&base( -$radix.im².Int, :precision($precision) ).split: '.';
    $_ //= '' for $re-fr, $im-fr;

    sub zip (Str $a, Str $b) {
        my $l = '0' x ($a.chars - $b.chars).abs;
        ([~] flat ($a~$l).comb Z flat ($b~$l).comb).subst(/ '0'+ $ /, '') || '0'
    }

    my $whole = flip zip $re-wh.flip, $im-wh.flip;
    my $fraction = zip $im-fr, $re-fr;
    $fraction eq 0 ?? "$whole" !! "$whole.$fraction"
}

multi sub parse-base (Str $str, Complex $radix where *.re == 0) {
    return -1 * $str.substr(1).&parse-base($radix) if $str.substr(0,1) eq '-';
    my ($whole, $frac) = $str.split: '.';
    my $fraction = 0;
    $fraction = [+] $frac.comb.kv.map: { $^v.parse-base($radix.im².Int) * $radix ** -($^k+1) } if $frac;
    $fraction + [+] $whole.flip.comb.kv.map: { $^v.parse-base($radix.im².Int) * $radix ** $^k }
}

# TESTING
for 0, 2i, 1, 2i, 5, 2i, -13, 2i, 9i, 2i, -3i, 2i, 7.75-7.5i, 2i, .25, 2i, # base 2i tests
    5+5i,  2i, 5+5i,  3i, 5+5i,  4i, 5+5i,  5i, 5+5i,  6i, # same value, positive imaginary bases
    5+5i, -2i, 5+5i, -3i, 5+5i, -4i, 5+5i, -5i, 5+5i, -6i, # same value, negative imaginary bases
    227.65625+10.859375i, 4i, # larger test value
    31433.3487654321-2902.4480452675i, 6i # heh
  -> $v, $r {
my $ibase = $v.&base($r, :precision(-6));
printf "%33s.&base\(%2si\) = %-11s : %13s.&parse-base\(%2si\) = %s\n",
  $v, $r.im, $ibase, "'$ibase'", $r.im, $ibase.&parse-base($r).round(1e-10).narrow;
}
Output:
                                0.&base( 2i) = 0           :           '0'.&parse-base( 2i) = 0
                                1.&base( 2i) = 1           :           '1'.&parse-base( 2i) = 1
                                5.&base( 2i) = 10301       :       '10301'.&parse-base( 2i) = 5
                              -13.&base( 2i) = 1030003     :     '1030003'.&parse-base( 2i) = -13
                             0+9i.&base( 2i) = 103010.2    :    '103010.2'.&parse-base( 2i) = 0+9i
                            -0-3i.&base( 2i) = 1030.2      :      '1030.2'.&parse-base( 2i) = 0-3i
                        7.75-7.5i.&base( 2i) = 11210.31    :    '11210.31'.&parse-base( 2i) = 7.75-7.5i
                             0.25.&base( 2i) = 1.03        :        '1.03'.&parse-base( 2i) = 0.25
                             5+5i.&base( 2i) = 10331.2     :     '10331.2'.&parse-base( 2i) = 5+5i
                             5+5i.&base( 3i) = 25.3        :        '25.3'.&parse-base( 3i) = 5+5i
                             5+5i.&base( 4i) = 25.C        :        '25.C'.&parse-base( 4i) = 5+5i
                             5+5i.&base( 5i) = 15          :          '15'.&parse-base( 5i) = 5+5i
                             5+5i.&base( 6i) = 15.6        :        '15.6'.&parse-base( 6i) = 5+5i
                             5+5i.&base(-2i) = 11321.2     :     '11321.2'.&parse-base(-2i) = 5+5i
                             5+5i.&base(-3i) = 1085.6      :      '1085.6'.&parse-base(-3i) = 5+5i
                             5+5i.&base(-4i) = 10F5.4      :      '10F5.4'.&parse-base(-4i) = 5+5i
                             5+5i.&base(-5i) = 10O5        :        '10O5'.&parse-base(-5i) = 5+5i
                             5+5i.&base(-6i) = 5.U         :         '5.U'.&parse-base(-6i) = 5+5i
             227.65625+10.859375i.&base( 4i) = 10234.5678  :  '10234.5678'.&parse-base( 4i) = 227.65625+10.859375i
31433.3487654321-2902.4480452675i.&base( 6i) = PERL6.ROCKS : 'PERL6.ROCKS'.&parse-base( 6i) = 31433.3487654321-2902.4480452675i

Module

Works with: Rakudo version 2020.02

Using the module Base::Any from the Raku ecosystem.

Does everything the explicit version does but also handles a much larger range of imaginary bases.

Doing pretty much the same tests as the explicit version.

use Base::Any;

# TESTING
for 0, 2i, 1, 2i, 5, 2i, -13, 2i, 9i, 2i, -3i, 2i, 7.75-7.5i, 2i, .25, 2i, # base 2i tests
    5+5i,  2i, 5+5i,  3i, 5+5i,  4i, 5+5i,  5i, 5+5i,  6i, # same value, positive imaginary bases
    5+5i, -2i, 5+5i, -3i, 5+5i, -4i, 5+5i, -5i, 5+5i, -6i, # same value, negative imaginary bases
    227.65625+10.859375i, 4i, # larger test value
    31433.3487654321-2902.4480452675i, 6i, # heh
    -3544.29+26541.468i, -10i
  -> $v, $r {
    my $ibase = $v.&to-base($r, :precision(-6));
    printf "%33s.&to-base\(%3si\) = %-11s : %13s.&from-base\(%3si\) = %s\n",
    $v, $r.im, $ibase, "'$ibase'", $r.im, $ibase.&from-base($r).round(1e-10).narrow;
}
Output:
                                0.&to-base(  2i) = 0           :           '0'.&from-base(  2i) = 0
                                1.&to-base(  2i) = 1           :           '1'.&from-base(  2i) = 1
                                5.&to-base(  2i) = 10301       :       '10301'.&from-base(  2i) = 5
                              -13.&to-base(  2i) = 1030003     :     '1030003'.&from-base(  2i) = -13
                             0+9i.&to-base(  2i) = 103010.2    :    '103010.2'.&from-base(  2i) = 0+9i
                            -0-3i.&to-base(  2i) = 1030.2      :      '1030.2'.&from-base(  2i) = 0-3i
                        7.75-7.5i.&to-base(  2i) = 11210.31    :    '11210.31'.&from-base(  2i) = 7.75-7.5i
                             0.25.&to-base(  2i) = 1.03        :        '1.03'.&from-base(  2i) = 0.25
                             5+5i.&to-base(  2i) = 10331.2     :     '10331.2'.&from-base(  2i) = 5+5i
                             5+5i.&to-base(  3i) = 25.3        :        '25.3'.&from-base(  3i) = 5+5i
                             5+5i.&to-base(  4i) = 25.C        :        '25.C'.&from-base(  4i) = 5+5i
                             5+5i.&to-base(  5i) = 15          :          '15'.&from-base(  5i) = 5+5i
                             5+5i.&to-base(  6i) = 15.6        :        '15.6'.&from-base(  6i) = 5+5i
                             5+5i.&to-base( -2i) = 11321.2     :     '11321.2'.&from-base( -2i) = 5+5i
                             5+5i.&to-base( -3i) = 1085.6      :      '1085.6'.&from-base( -3i) = 5+5i
                             5+5i.&to-base( -4i) = 10F5.4      :      '10F5.4'.&from-base( -4i) = 5+5i
                             5+5i.&to-base( -5i) = 10O5        :        '10O5'.&from-base( -5i) = 5+5i
                             5+5i.&to-base( -6i) = 5.U         :         '5.U'.&from-base( -6i) = 5+5i
             227.65625+10.859375i.&to-base(  4i) = 10234.5678  :  '10234.5678'.&from-base(  4i) = 227.65625+10.859375i
31433.3487654321-2902.4480452675i.&to-base(  6i) = PERL6.ROCKS : 'PERL6.ROCKS'.&from-base(  6i) = 31433.3487654321-2902.4480452675i
              -3544.29+26541.468i.&to-base(-10i) = Raku.FTW    :    'Raku.FTW'.&from-base(-10i) = -3544.29+26541.468i

Ruby

Works with: Ruby version 2.3

The Functions

# Convert a quarter-imaginary base value (as a string) into a base 10 Gaussian integer.

def base2i_decode(qi)
  return 0 if qi == '0'
  md = qi.match(/^(?<int>[0-3]+)(?:\.(?<frc>[0-3]+))?$/)
  raise 'ill-formed quarter-imaginary base value' if !md
  ls_pow = md[:frc] ? -(md[:frc].length) : 0
  value = 0
  (md[:int] + (md[:frc] ? md[:frc] : '')).reverse.each_char.with_index do |dig, inx|
    value += dig.to_i * (2i)**(inx + ls_pow)
  end
  return value
end

# Convert a base 10 Gaussian integer into a quarter-imaginary base value (as a string).

def base2i_encode(gi)
  odd = gi.imag.to_i.odd?
  frac = (gi.imag.to_i != 0)
  real = gi.real.to_i
  imag = (gi.imag.to_i + 1) / 2
  value = ''
  phase_real = true
  while (real != 0) || (imag != 0)
    if phase_real
      real, rem = real.divmod(4)
      real = -real
    else
      imag, rem = imag.divmod(4)
      imag = -imag
    end
    value.prepend(rem.to_s)
    phase_real = !phase_real
  end
  value = '0' if value == ''
  value.concat(odd ? '.2' : '.0') if frac
  return value
end

The Task

# Extend class Integer with a string conveter.

class Integer
  def as_str()
    return to_s()
  end
end

# Extend class Complex with a string conveter (works only with Gaussian integers).

class Complex
  def as_str()
    return '0' if self == 0
    return real.to_i.to_s if imag == 0
    return imag.to_i.to_s + 'i' if real == 0
    return real.to_i.to_s + '+' + imag.to_i.to_s + 'i' if imag >= 0
    return real.to_i.to_s + '-' + (-(imag.to_i)).to_s + 'i'
  end
end

# Emit various tables of conversions.

1.step(16) do |gi|
  puts(" %4s -> %8s -> %4s     %4s -> %8s -> %4s" %
    [gi.as_str, base2i_encode(gi), base2i_decode(base2i_encode(gi)).as_str,
     (-gi).as_str, base2i_encode(-gi), base2i_decode(base2i_encode(-gi)).as_str])
end
puts
1.step(16) do |gi|
  gi *= 0+1i
  puts(" %4s -> %8s -> %4s     %4s -> %8s -> %4s" %
    [gi.as_str, base2i_encode(gi), base2i_decode(base2i_encode(gi)).as_str,
     (-gi).as_str, base2i_encode(-gi), base2i_decode(base2i_encode(-gi)).as_str])
end
puts
0.step(3) do |m|
  0.step(3) do |l|
    0.step(3) do |h|
      qi = (100 * h + 10 * m + l).to_s
      gi = base2i_decode(qi)
      md = base2i_encode(gi).match(/^(?<num>[0-3]+)(?:\.0)?$/)
      print(" %4s -> %6s -> %4s" % [qi, gi.as_str, md[:num]])
    end
    puts
  end
end
Output:

Conversions given in the task.

    1 ->        1 ->    1       -1 ->      103 ->   -1
    2 ->        2 ->    2       -2 ->      102 ->   -2
    3 ->        3 ->    3       -3 ->      101 ->   -3
    4 ->    10300 ->    4       -4 ->      100 ->   -4
    5 ->    10301 ->    5       -5 ->      203 ->   -5
    6 ->    10302 ->    6       -6 ->      202 ->   -6
    7 ->    10303 ->    7       -7 ->      201 ->   -7
    8 ->    10200 ->    8       -8 ->      200 ->   -8
    9 ->    10201 ->    9       -9 ->      303 ->   -9
   10 ->    10202 ->   10      -10 ->      302 ->  -10
   11 ->    10203 ->   11      -11 ->      301 ->  -11
   12 ->    10100 ->   12      -12 ->      300 ->  -12
   13 ->    10101 ->   13      -13 ->  1030003 ->  -13
   14 ->    10102 ->   14      -14 ->  1030002 ->  -14
   15 ->    10103 ->   15      -15 ->  1030001 ->  -15
   16 ->    10000 ->   16      -16 ->  1030000 ->  -16

   1i ->     10.2 ->   1i      -1i ->      0.2 ->  -1i
   2i ->     10.0 ->   2i      -2i ->   1030.0 ->  -2i
   3i ->     20.2 ->   3i      -3i ->   1030.2 ->  -3i
   4i ->     20.0 ->   4i      -4i ->   1020.0 ->  -4i
   5i ->     30.2 ->   5i      -5i ->   1020.2 ->  -5i
   6i ->     30.0 ->   6i      -6i ->   1010.0 ->  -6i
   7i -> 103000.2 ->   7i      -7i ->   1010.2 ->  -7i
   8i -> 103000.0 ->   8i      -8i ->   1000.0 ->  -8i
   9i -> 103010.2 ->   9i      -9i ->   1000.2 ->  -9i
  10i -> 103010.0 ->  10i     -10i ->   2030.0 -> -10i
  11i -> 103020.2 ->  11i     -11i ->   2030.2 -> -11i
  12i -> 103020.0 ->  12i     -12i ->   2020.0 -> -12i
  13i -> 103030.2 ->  13i     -13i ->   2020.2 -> -13i
  14i -> 103030.0 ->  14i     -14i ->   2010.0 -> -14i
  15i -> 102000.2 ->  15i     -15i ->   2010.2 -> -15i
  16i -> 102000.0 ->  16i     -16i ->   2000.0 -> -16i
Output:

From the OEIS Wiki [1].

    0 ->      0 ->    0  100 ->     -4 ->  100  200 ->     -8 ->  200  300 ->    -12 ->  300
    1 ->      1 ->    1  101 ->     -3 ->  101  201 ->     -7 ->  201  301 ->    -11 ->  301
    2 ->      2 ->    2  102 ->     -2 ->  102  202 ->     -6 ->  202  302 ->    -10 ->  302
    3 ->      3 ->    3  103 ->     -1 ->  103  203 ->     -5 ->  203  303 ->     -9 ->  303
   10 ->     2i ->   10  110 ->  -4+2i ->  110  210 ->  -8+2i ->  210  310 -> -12+2i ->  310
   11 ->   1+2i ->   11  111 ->  -3+2i ->  111  211 ->  -7+2i ->  211  311 -> -11+2i ->  311
   12 ->   2+2i ->   12  112 ->  -2+2i ->  112  212 ->  -6+2i ->  212  312 -> -10+2i ->  312
   13 ->   3+2i ->   13  113 ->  -1+2i ->  113  213 ->  -5+2i ->  213  313 ->  -9+2i ->  313
   20 ->     4i ->   20  120 ->  -4+4i ->  120  220 ->  -8+4i ->  220  320 -> -12+4i ->  320
   21 ->   1+4i ->   21  121 ->  -3+4i ->  121  221 ->  -7+4i ->  221  321 -> -11+4i ->  321
   22 ->   2+4i ->   22  122 ->  -2+4i ->  122  222 ->  -6+4i ->  222  322 -> -10+4i ->  322
   23 ->   3+4i ->   23  123 ->  -1+4i ->  123  223 ->  -5+4i ->  223  323 ->  -9+4i ->  323
   30 ->     6i ->   30  130 ->  -4+6i ->  130  230 ->  -8+6i ->  230  330 -> -12+6i ->  330
   31 ->   1+6i ->   31  131 ->  -3+6i ->  131  231 ->  -7+6i ->  231  331 -> -11+6i ->  331
   32 ->   2+6i ->   32  132 ->  -2+6i ->  132  232 ->  -6+6i ->  232  332 -> -10+6i ->  332
   33 ->   3+6i ->   33  133 ->  -1+6i ->  133  233 ->  -5+6i ->  233  333 ->  -9+6i ->  333

Sidef

Translation of: Raku
func base (Number num, Number radix { _ ~~ (-36 .. -2) }, precision = -15) -> String {
    num || return '0'

    var place  = 0
    var result = ''
    var value  = num
    var upper_bound = 1/(-radix + 1)
    var lower_bound = radix*upper_bound

    while (!(lower_bound <= value) || !(value < upper_bound)) {
        value = num/(radix**++place)
    }

    while ((value || (place > 0)) && (place > precision)) {
        var digit = (radix*value - lower_bound -> int)
        value    =  (radix*value - digit)
        result += '.' if (!place && !result.contains('.'))
        result += ((digit == -radix) ? (digit-1 -> base(-radix) + '0') : digit.base(-radix))
        place--
    }

    return result
}

func base (Number num, Number radix { .re == 0 }, precision = -8) -> String {

    (radix.im.abs ~~ 2..6) || die "Base #{radix} out of range"

    var (re, im)          = (num.re, num.im)
    var (re_wh, re_fr='') = base(re,          -radix.im**2, precision).split('.')...
    var (im_wh, im_fr='') = base(im/radix.im, -radix.im**2, precision).split('.')...

    func zip (String a, String b) {
        var l = ('0' * abs(a.len - b.len))
        chars(a+l) ~Z chars(b+l) -> flat.join.sub(/0+\z/, '') || '0'
    }

    var whole = zip(re_wh.flip, im_wh.flip).flip
    var fraction = zip(im_fr, re_fr)
    fraction == '0' ? whole : "#{whole}.#{fraction}"
}

func parse_base (String str, Number radix { .re == 0 }) -> Number {

    if (str.char(0) == '-') {
        return (-1 * parse_base(str.substr(1), radix))
    }

    var (whole, frac='') = str.split('.')...

    var fraction = frac.chars.map_kv {|k,v|
        Number(v, radix.im**2) * radix**-(k+1)
    }.sum

    fraction += whole.flip.chars.map_kv {|k,v|
        Number(v, radix.im**2) * radix**k
    }.sum

    return fraction
}

var tests = [0, 2i, 1, 2i, 5, 2i, -13, 2i, 9i, 2i, -3i, 2i, 7.75-7.5i, 2i, .25, 2i, # base 2i tests
    5+5i,  2i, 5+5i,  3i, 5+5i,  4i, 5+5i,  5i, 5+5i,  6i, # same value, positive imaginary bases
    5+5i, -2i, 5+5i, -3i, 5+5i, -4i, 5+5i, -5i, 5+5i, -6i, # same value, negative imaginary bases
    227.65625+10.859375i, 4i] # larger test value

tests.each_slice(2, {|v,r|
    var ibase = base(v, r)
    printf("base(%20s, %2si) = %-10s : parse_base(%12s, %2si) = %s\n",
        v, r.im, ibase, "'#{ibase}'", r.im, parse_base(ibase, r).round(-8))
})
Output:
base(                   0,  2i) = 0          : parse_base(         '0',  2i) = 0
base(                   1,  2i) = 1          : parse_base(         '1',  2i) = 1
base(                   5,  2i) = 10301      : parse_base(     '10301',  2i) = 5
base(                 -13,  2i) = 1030003    : parse_base(   '1030003',  2i) = -13
base(                  9i,  2i) = 103010.2   : parse_base(  '103010.2',  2i) = 9i
base(                 -3i,  2i) = 1030.2     : parse_base(    '1030.2',  2i) = -3i
base(           7.75-7.5i,  2i) = 11210.31   : parse_base(  '11210.31',  2i) = 7.75-7.5i
base(                0.25,  2i) = 1.03       : parse_base(      '1.03',  2i) = 0.25
base(                5+5i,  2i) = 10331.2    : parse_base(   '10331.2',  2i) = 5+5i
base(                5+5i,  3i) = 25.3       : parse_base(      '25.3',  3i) = 5+5i
base(                5+5i,  4i) = 25.c       : parse_base(      '25.c',  4i) = 5+5i
base(                5+5i,  5i) = 15         : parse_base(        '15',  5i) = 5+5i
base(                5+5i,  6i) = 15.6       : parse_base(      '15.6',  6i) = 5+5i
base(                5+5i, -2i) = 11321.2    : parse_base(   '11321.2', -2i) = 5+5i
base(                5+5i, -3i) = 1085.6     : parse_base(    '1085.6', -3i) = 5+5i
base(                5+5i, -4i) = 10f5.4     : parse_base(    '10f5.4', -4i) = 5+5i
base(                5+5i, -5i) = 10o5       : parse_base(      '10o5', -5i) = 5+5i
base(                5+5i, -6i) = 5.u        : parse_base(       '5.u', -6i) = 5+5i
base(227.65625+10.859375i,  4i) = 10234.5678 : parse_base('10234.5678',  4i) = 227.65625+10.859375i

Visual Basic .NET

Translation of: C#
Imports System.Text

Module Module1

    Class Complex : Implements IFormattable
        Private ReadOnly real As Double
        Private ReadOnly imag As Double

        Public Sub New(r As Double, i As Double)
            real = r
            imag = i
        End Sub

        Public Sub New(r As Integer, i As Integer)
            real = r
            imag = i
        End Sub

        Public Function Inv() As Complex
            Dim denom = real * real + imag * imag
            Return New Complex(real / denom, -imag / denom)
        End Function

        Public Shared Operator -(self As Complex) As Complex
            Return New Complex(-self.real, -self.imag)
        End Operator

        Public Shared Operator +(lhs As Complex, rhs As Complex) As Complex
            Return New Complex(lhs.real + rhs.real, lhs.imag + rhs.imag)
        End Operator

        Public Shared Operator -(lhs As Complex, rhs As Complex) As Complex
            Return New Complex(lhs.real - rhs.real, lhs.imag - rhs.imag)
        End Operator

        Public Shared Operator *(lhs As Complex, rhs As Complex) As Complex
            Return New Complex(lhs.real * rhs.real - lhs.imag * rhs.imag, lhs.real * rhs.imag + lhs.imag * rhs.real)
        End Operator

        Public Shared Operator /(lhs As Complex, rhs As Complex) As Complex
            Return lhs * rhs.Inv
        End Operator

        Public Shared Operator *(lhs As Complex, rhs As Double) As Complex
            Return New Complex(lhs.real * rhs, lhs.imag * rhs)
        End Operator

        Public Function ToQuaterImaginary() As QuaterImaginary
            If real = 0.0 AndAlso imag = 0.0 Then
                Return New QuaterImaginary("0")
            End If
            Dim re = CType(real, Integer)
            Dim im = CType(imag, Integer)
            Dim fi = -1
            Dim sb As New StringBuilder
            While re <> 0
                Dim rm = re Mod -4
                re \= -4
                If rm < 0 Then
                    rm += 4
                    re += 1
                End If
                sb.Append(rm)
                sb.Append(0)
            End While
            If im <> 0 Then
                Dim f = (New Complex(0.0, imag) / New Complex(0.0, 2.0)).real
                im = Math.Ceiling(f)
                f = -4.0 * (f - im)
                Dim index = 1
                While im <> 0
                    Dim rm = im Mod -4
                    im \= -4
                    If rm < 0 Then
                        rm += 4
                        im += 1
                    End If
                    If index < sb.Length Then
                        sb(index) = Chr(rm + 48)
                    Else
                        sb.Append(0)
                        sb.Append(rm)
                    End If
                    index += 2
                End While
                fi = f
            End If
            Dim reverse As New String(sb.ToString().Reverse().ToArray())
            sb.Length = 0
            sb.Append(reverse)
            If fi <> -1 Then
                sb.AppendFormat(".{0}", fi)
            End If
            Dim s = sb.ToString().TrimStart("0")
            If s(0) = "." Then
                s = "0" + s
            End If
            Return New QuaterImaginary(s)
        End Function

        Public Overloads Function ToString() As String
            Dim r2 = If(real = -0.0, 0.0, real) 'get rid of negative zero
            Dim i2 = If(imag = -0.0, 0.0, imag) 'ditto
            If i2 = 0.0 Then
                Return String.Format("{0}", r2)
            End If
            If r2 = 0.0 Then
                Return String.Format("{0}i", i2)
            End If
            If i2 > 0.0 Then
                Return String.Format("{0} + {1}i", r2, i2)
            End If
            Return String.Format("{0} - {1}i", r2, -i2)
        End Function

        Public Overloads Function ToString(format As String, formatProvider As IFormatProvider) As String Implements IFormattable.ToString
            Return ToString()
        End Function
    End Class

    Class QuaterImaginary
        Private Shared ReadOnly twoI = New Complex(0.0, 2.0)
        Private Shared ReadOnly invTwoI = twoI.Inv()

        Private ReadOnly b2i As String

        Public Sub New(b2i As String)
            If b2i = "" OrElse Not b2i.All(Function(c) "0123.".IndexOf(c) > -1) OrElse b2i.Count(Function(c) c = ".") > 1 Then
                Throw New Exception("Invalid Base 2i number")
            End If
            Me.b2i = b2i
        End Sub

        Public Function ToComplex() As Complex
            Dim pointPos = b2i.IndexOf(".")
            Dim posLen = If(pointPos <> -1, pointPos, b2i.Length)
            Dim sum = New Complex(0.0, 0.0)
            Dim prod = New Complex(1.0, 0.0)
            For j = 0 To posLen - 1
                Dim k = Asc(b2i(posLen - 1 - j)) - Asc("0")
                If k > 0.0 Then
                    sum += prod * k
                End If
                prod *= twoI
            Next
            If pointPos <> -1 Then
                prod = invTwoI
                For j = posLen + 1 To b2i.Length - 1
                    Dim k = Asc(b2i(j)) - Asc("0")
                    If k > 0.0 Then
                        sum += prod * k
                    End If
                    prod *= invTwoI
                Next
            End If
            Return sum
        End Function

        Public Overrides Function ToString() As String
            Return b2i
        End Function
    End Class

    Sub Main()
        For i = 1 To 16
            Dim c1 As New Complex(i, 0)
            Dim qi = c1.ToQuaterImaginary()
            Dim c2 = qi.ToComplex()
            Console.Write("{0,4} -> {1,8} -> {2,4}     ", c1, qi, c2)
            c1 = -c1
            qi = c1.ToQuaterImaginary()
            c2 = qi.ToComplex()
            Console.WriteLine("{0,4} -> {1,8} -> {2,4}", c1, qi, c2)
        Next
        Console.WriteLine()
        For i = 1 To 16
            Dim c1 As New Complex(0, i)
            Dim qi = c1.ToQuaterImaginary()
            Dim c2 = qi.ToComplex()
            Console.Write("{0,4} -> {1,8} -> {2,4}     ", c1, qi, c2)
            c1 = -c1
            qi = c1.ToQuaterImaginary()
            c2 = qi.ToComplex()
            Console.WriteLine("{0,4} -> {1,8} -> {2,4}", c1, qi, c2)
        Next
    End Sub

End Module
Output:
   1 ->        1 ->    1       -1 ->      103 ->   -1
   2 ->        2 ->    2       -2 ->      102 ->   -2
   3 ->        3 ->    3       -3 ->      101 ->   -3
   4 ->    10300 ->    4       -4 ->      100 ->   -4
   5 ->    10301 ->    5       -5 ->      203 ->   -5
   6 ->    10302 ->    6       -6 ->      202 ->   -6
   7 ->    10303 ->    7       -7 ->      201 ->   -7
   8 ->    10200 ->    8       -8 ->      200 ->   -8
   9 ->    10201 ->    9       -9 ->      303 ->   -9
  10 ->    10202 ->   10      -10 ->      302 ->  -10
  11 ->    10203 ->   11      -11 ->      301 ->  -11
  12 ->    10100 ->   12      -12 ->      300 ->  -12
  13 ->    10101 ->   13      -13 ->  1030003 ->  -13
  14 ->    10102 ->   14      -14 ->  1030002 ->  -14
  15 ->    10103 ->   15      -15 ->  1030001 ->  -15
  16 ->    10000 ->   16      -16 ->  1030000 ->  -16

  1i ->     10.2 ->   1i      -1i ->      0.2 ->  -1i
  2i ->     10.0 ->   2i      -2i ->   1030.0 ->  -2i
  3i ->     20.2 ->   3i      -3i ->   1030.2 ->  -3i
  4i ->     20.0 ->   4i      -4i ->   1020.0 ->  -4i
  5i ->     30.2 ->   5i      -5i ->   1020.2 ->  -5i
  6i ->     30.0 ->   6i      -6i ->   1010.0 ->  -6i
  7i -> 103000.2 ->   7i      -7i ->   1010.2 ->  -7i
  8i -> 103000.0 ->   8i      -8i ->   1000.0 ->  -8i
  9i -> 103010.2 ->   9i      -9i ->   1000.2 ->  -9i
 10i -> 103010.0 ->  10i     -10i ->   2030.0 -> -10i
 11i -> 103020.2 ->  11i     -11i ->   2030.2 -> -11i
 12i -> 103020.0 ->  12i     -12i ->   2020.0 -> -12i
 13i -> 103030.2 ->  13i     -13i ->   2020.2 -> -13i
 14i -> 103030.0 ->  14i     -14i ->   2010.0 -> -14i
 15i -> 102000.2 ->  15i     -15i ->   2010.2 -> -15i
 16i -> 102000.0 ->  16i     -16i ->   2000.0 -> -16i

Wren

Translation of: Go
Library: Wren-complex
Library: Wren-fmt
import "./complex" for Complex
import "./fmt" for Fmt

class QuaterImaginary {
    construct new(b2i) {
       if (b2i.type != String || b2i == "" || !b2i.all { |d| "0123.".contains(d) } ||
           b2i.count { |d| d == "." } > 1) Fiber.abort("Invalid Base 2i number.")
        _b2i = b2i
    }

    // only works properly if 'c.real' and 'c.imag' are both integral
    static fromComplex(c) {
        if (c.real == 0 && c.imag == 0) return QuaterImaginary.new("0")
        var re = c.real.truncate
        var im = c.imag.truncate
        var fi = -1
        var sb = ""
        while (re != 0) {
            var rem = re % (-4)
            re = (re/(-4)).truncate
            if (rem < 0) {
                rem = 4 + rem
                re = re + 1
            }
            if (rem == -0) rem = 0 // get rid of minus zero
            sb = sb + rem.toString + "0"
        }
        if (im != 0) {
            var f = (Complex.new(0, c.imag) / Complex.imagTwo).real
            im = f.ceil
            f = -4 * (f - im)
            var index = 1
            while (im != 0) {
                var rem = im % (-4)
                im = (im/(-4)).truncate
                if (rem < 0) {
                    rem = 4 + rem
                    im = im + 1
                }
                if (index < sb.count) {
                    var sbl = sb.toList
                    sbl[index] = String.fromByte(rem + 48)
                    sb = sbl.join()
                } else {
                    if (rem == -0) rem = 0 // get rid of minus zero
                    sb = sb + "0" + rem.toString
                }
                index = index + 2
            }
            fi = f.truncate
        }
        if (sb.count > 0) sb = sb[-1..0]
        if (fi != -1) {
            if (fi == -0) fi = 0 // get rid of minus zero
            sb = sb + ".%(fi)"
        }
        sb = sb.trimStart("0")
        if (sb.startsWith(".")) sb = "0" + sb
        return QuaterImaginary.new(sb)
    }

    toComplex {
        var pointPos = _b2i.indexOf(".")
        var posLen = (pointPos != -1) ? pointPos : _b2i.count
        var sum = Complex.zero
        var prod = Complex.one
        for (j in 0...posLen) {
            var k = _b2i.bytes[posLen-1-j] - 48
            if (k > 0) sum = sum + prod * k
            prod = prod * Complex.imagTwo
        }
        if (pointPos != -1) {
            prod = Complex.imagTwo.inverse
            var j = posLen + 1
            while (j < _b2i.count) {
                var k = _b2i.bytes[j] - 48
                if (k > 0) sum = sum + prod * k
                prod = prod / Complex.imagTwo
                j = j + 1
            }
        }
        return sum
    }

    toString { _b2i }
}

var imagOnly = Fn.new { |c| c.imag.toString + "i" }

var fmt = "$4s -> $8s -> $4s"
Complex.showAsReal = true
for (i in 1..16) {
    var c1 = Complex.new(i, 0)
    var qi = QuaterImaginary.fromComplex(c1)
    var c2 = qi.toComplex
    Fmt.write("%(fmt)     ", c1, qi, c2)
    c1 = -c1
    qi = QuaterImaginary.fromComplex(c1)
    c2 = qi.toComplex
    Fmt.print(fmt, c1, qi, c2)
}
System.print()
for (i in 1..16) {
    var c1 = Complex.new(0, i)
    var qi = QuaterImaginary.fromComplex(c1)
    var c2 = qi.toComplex
    Fmt.write("%(fmt)     ", imagOnly.call(c1), qi, imagOnly.call(c2))
    c1 = -c1
    qi = QuaterImaginary.fromComplex(c1)
    c2 = qi.toComplex
    Fmt.print(fmt, imagOnly.call(c1), qi, imagOnly.call(c2))
}
Output:
   1 ->        1 ->    1       -1 ->      103 ->   -1
   2 ->        2 ->    2       -2 ->      102 ->   -2
   3 ->        3 ->    3       -3 ->      101 ->   -3
   4 ->    10300 ->    4       -4 ->      100 ->   -4
   5 ->    10301 ->    5       -5 ->      203 ->   -5
   6 ->    10302 ->    6       -6 ->      202 ->   -6
   7 ->    10303 ->    7       -7 ->      201 ->   -7
   8 ->    10200 ->    8       -8 ->      200 ->   -8
   9 ->    10201 ->    9       -9 ->      303 ->   -9
  10 ->    10202 ->   10      -10 ->      302 ->  -10
  11 ->    10203 ->   11      -11 ->      301 ->  -11
  12 ->    10100 ->   12      -12 ->      300 ->  -12
  13 ->    10101 ->   13      -13 ->  1030003 ->  -13
  14 ->    10102 ->   14      -14 ->  1030002 ->  -14
  15 ->    10103 ->   15      -15 ->  1030001 ->  -15
  16 ->    10000 ->   16      -16 ->  1030000 ->  -16

  1i ->     10.2 ->   1i      -1i ->      0.2 ->  -1i
  2i ->     10.0 ->   2i      -2i ->   1030.0 ->  -2i
  3i ->     20.2 ->   3i      -3i ->   1030.2 ->  -3i
  4i ->     20.0 ->   4i      -4i ->   1020.0 ->  -4i
  5i ->     30.2 ->   5i      -5i ->   1020.2 ->  -5i
  6i ->     30.0 ->   6i      -6i ->   1010.0 ->  -6i
  7i -> 103000.2 ->   7i      -7i ->   1010.2 ->  -7i
  8i -> 103000.0 ->   8i      -8i ->   1000.0 ->  -8i
  9i -> 103010.2 ->   9i      -9i ->   1000.2 ->  -9i
 10i -> 103010.0 ->  10i     -10i ->   2030.0 -> -10i
 11i -> 103020.2 ->  11i     -11i ->   2030.2 -> -11i
 12i -> 103020.0 ->  12i     -12i ->   2020.0 -> -12i
 13i -> 103030.2 ->  13i     -13i ->   2020.2 -> -13i
 14i -> 103030.0 ->  14i     -14i ->   2010.0 -> -14i
 15i -> 102000.2 ->  15i     -15i ->   2010.2 -> -15i
 16i -> 102000.0 ->  16i     -16i ->   2000.0 -> -16i