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

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

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

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

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

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

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

See Wikipedia: Quater-imaginary\_base for more details.

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

|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| | Base 10 | Base 2*i* | | --- | --- | | 1 | 1 | | 2 | 2 | | 3 | 3 | | 4 | 10300 | | 5 | 10301 | | 6 | 10302 | | 7 | 10303 | | 8 | 10200 | | 9 | 10201 | | 10 | 10202 | | 11 | 10203 | | 12 | 10100 | | 13 | 10101 | | 14 | 10102 | | 15 | 10103 | | 16 | 10000 | | | Base 10 | Base 2*i* | | --- | --- | | −1 | 103 | | −2 | 102 | | −3 | 101 | | −4 | 100 | | −5 | 203 | | −6 | 202 | | −7 | 201 | | −8 | 200 | | −9 | 303 | | −10 | 302 | | −11 | 301 | | −12 | 300 | | −13 | 1030003 | | −14 | 1030002 | | −15 | 1030001 | | −16 | 1030000 | | | Base 10 | Base 2*i* | | --- | --- | | 1*i* | 10.2 | | 2*i* | 10.0 | | 3*i* | 20.2 | | 4*i* | 20.0 | | 5*i* | 30.2 | | 6*i* | 30.0 | | 7*i* | 103000.2 | | 8*i* | 103000.0 | | 9*i* | 103010.2 | | 10*i* | 103010.0 | | 11*i* | 103020.2 | | 12*i* | 103020.0 | | 13*i* | 103030.2 | | 14*i* | 103030.0 | | 15*i* | 102000.2 | | 16*i* | 102000.0 | | | Base 10 | Base 2*i* | | --- | --- | | −1*i* | 0.2 | | −2*i* | 1030.0 | | −3*i* | 1030.2 | | −4*i* | 1020.0 | | −5*i* | 1020.2 | | −6*i* | 1010.0 | | −7*i* | 1010.2 | | −8*i* | 1000.0 | | −9*i* | 1000.2 | | −10*i* | 2030.0 | | −11*i* | 2030.2 | | −12*i* | 2020.0 | | −13*i* | 2020.2 | | −14*i* | 2010.0 | | −15*i* | 2010.2 | | −16*i* | 2000.0 | |
assistant
(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
```