Peter
Peter Campbell Smith

Imaginaries and the daily grind

Weekly challenge 178 — 15 August 2022

Week 178 - 15 Aug 2022

Task 1

Task — Quater-imaginary base

Write a script to convert a given number base 10 to quater-imaginary base and vice-versa. For more information, please see this Wikipedia page.

In summary, quater-imaginary base is base 2i or sqrt(-4).

Examples


Example 1:
$base_10 = 4
$quater_imaginary_base = 10300
as that is 1 x (2i)4 + 3 x (2i)2, which is  8 - 4 = 4

Analysis

Quater-imaginary base numbers - henceforth qibs - are numbers written in the base 2i. For the purposes of the blog I am going to show qibs in red, for example 301.1. Note that the digits of a qib can only be 0, 1, 2 or 3.

So, for example:

123 = 1 x (2i)^2 + 2 x 2i + 3 = - 4 + 4i + 3 = -1 + 4i

I thought at first that I could just translate the Wikipedia explanation into Perl, but that turned out quite tricky. I also though about using Math::Complex, but decided - probably wrongly - that it wasn't worth the effort of relearning its rather unfamiliar syntax.

So I wrote my own functions to_qib and from_qib. It wasn't that hard, though perhaps not as much fun as I might have hoped. I confined myself to complex numbers with integer coefficients, which means the qibs either end in .0 or .2.

The length of qibs increases quite rapidly and my rather clunky way of adding them means that my code won't handle them over 18 digits before the point - but a little tweaking could probably cure that.

Try it 

Try running the script with any input - you can enter either a base 10 integer or a qib or both:


+ i
example: 42 or 42 - 21i or 84i



example: 10330

Script


#!/usr/bin/perl

# Peter Campbell Smith - 2022-08-15
# PWC 178 task 1

use v5.26;
use utf8;
use warnings;
use POSIX 'floor';

my (@tests, $test, $real_part, $imag_part, $result, $real, $imag, $qib);

@tests = ([5, 0], [0, 5], [4, 6], [35, 23], [-7, -10], [52, -77], [-1, 4]);
#          5       5i      4+6i    35+23i    -7-10i     52-77i     -1+4i

for $test (@tests) {
    $qib = to_qib(@$test);
    $result = from_qib($qib);
    say '';
}

sub to_qib {
    
    # converts a complex number with integer coefficients to a quater-imaginary base number
    
    my ($real, $imag, $real_part, $imag_part, $result);
    
    ($real, $imag) = @_;
    
    # convert real part and -2 x imag part to qib numbers
    $real_part = quater_imag($real) . '.0';
    $imag_part = quater_imag(-$imag * 2);
    
    # right shift the imag part (so dividing it by 2i)
    $imag_part =~ m|(.*)(.)|;
    $imag_part = qq[$1.$2]; 
    
    # add the real and imag parts
    $result = quater_add($real_part, $imag_part);
    say qq[$real + ${imag}i = [$result]2i];
    return $result;
}
    
sub from_qib {
    
    # converts a quater-imaginary base number to a complex number with integer coefficients

    my ($qib, @digits, $real, $imag, $real_mult, $imag_mult, $hold, $digit);

    $qib = $_[0];
    
    # put the digits into an array
    @digits = split(//, $qib);
    
    # loop over the digits from least significant upwards
    $imag = 0;
    $real = 0;
    $real_mult = 0;     # these are the contributions of the digit after the point
    $imag_mult = -0.5;
    for $digit (reverse @digits) {
        next if $digit eq '.';
        
        # add the (decimal) values of the digits to the answer
        $real += $real_mult * $digit;
        $imag += $imag_mult * $digit;
        
        # and get the real and imag contributions from the next digit
        $hold = $real_mult;
        $real_mult = -2 * $imag_mult;
        $imag_mult = 2 * $hold;
    }
    say qq[[$qib]2i = $real + ${imag}i];
}

sub quater_imag {
    
    # converts a real decimal number to a qib
    
    my ($number, $answer, $quotient, $remainder);
    
    # apply the 'Another conversion method' from Wikipedia
    $number = $_[0];
    $answer = '';
    
    # each division step contributes a remainder (0..3) and a following zero
    while (1) {
        ($quotient, $remainder) = div_minus4($number);
        $answer = $remainder . '0' . $answer;
        $number = $quotient;
        last unless $number != 0;
    }
    
    # remove the trailing zero
    return substr($answer, 0, -1);
}

sub div_minus4 {  

    # divides argument by -4 such that the remainder is always +ve
    
    my ($dividend, $quotient, $remainder);
    
    $dividend = $_[0];
    $quotient = -floor($dividend / 4);
    $remainder = $dividend + 4 * $quotient;
    return ($quotient, $remainder);
}

sub quater_add {
    
    # adds 2 qib numbers of which the first is real and the second is imaginary
    # both have one digit after the point
    
    my ($real, $imag, $result, $j);
    
    # left pad them with lots of zeroes so that corresponding digits are in the same place in the string
    ($real, $imag) = @_;
    $real = substr('00000000000000000000' . $real, -20);
    $imag = substr('00000000000000000000' . $imag, -20);
    
    # add them digit by digit
    for $j (0 .. 19) {
        if (substr($real, $j, 1) eq '.') {
            $result .= '.';
        } else {
            $result .= substr($real, $j, 1) + substr($imag, $j, 1);
        }
    }
    
    # get rid of the leading '0's
    $result =~ s|^0*||;
    return $result;
}
        

Output


Qibs are shown as [qib]2i

5 + 0i = [10301.0]2i
[10301.0]2i = 5 + 0i

0 + 5i = [30.2]2i
[30.2]2i = 0 + 5i

4 + 6i = [10330.0]2i
[10330.0]2i = 4 + 6i

35 + 23i = [121003.2]2i
[121003.2]2i = 35 + 23i

-7 + -10i = [2231.0]2i
[2231.0]2i = -7 + -10i

52 + -77i = [113202320.2]2i
[113202320.2]2i = 52 + -77i

-1 + 4i = [123.0]2i
[123.0]2i = -1 + 4i