Imaginary base numbers: Difference between revisions

From Rosetta Code
Content added Content deleted
(Fix links)
m (→‎{{header|Perl 6}}: Minor style tweaks, rename some variables for uniformity)
Line 307: Line 307:
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 since those bases will fit within standard base 36 digit representations. 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 here. Note that imaginary numbers are stored as floating point numbers in Perl 6 so some rounding error may creep in during calculations. The precision these conversion routines use is configurable; we are using 8 decimal places of precision here.
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 since those bases will fit within standard base 36 digit representations. 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 here. Note that imaginary numbers are stored as floating point numbers in Perl 6 so some rounding error may creep in during calculations. The precision these conversion routines use is configurable; we are using 8 decimal places of precision here.


<lang perl6>multi sub base ( Real $num, Int $radix where -37 < * < -1, :$precision = -8 ) {
<lang perl6>multi sub base ( Real $num, Int $radix where -37 < * < -1, :$precision = -15 ) {
return '0' unless $num;
return '0' unless $num;
my $value = $num;
my $value = $num;
Line 317: Line 317:
until $lower-bound <= $value < $upper-bound {
until $lower-bound <= $value < $upper-bound {
$place += 1;
$place += 1;
$value = $num / $radix ** $place;
$value = $num / $radix ** $place
}
}


Line 327: Line 327:
($digit-1).base(-$radix)~'0' !!
($digit-1).base(-$radix)~'0' !!
$digit.base(-$radix);
$digit.base(-$radix);
$place -= 1;
$place -= 1
}
}
$result

$result || '0';
}
}


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


Line 364: Line 362:
my ($whole, $frac) = $str.split: '.';
my ($whole, $frac) = $str.split: '.';
my $fraction = 0;
my $fraction = 0;
$fraction = [+] $frac.comb.kv.map: { $^v.parse-base(($radix.im²).Int) * $radix ** -($^k+1) } if $frac;
$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 }
$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, # base 2i
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, positive imaginary bases
5+5i, -2i, 5+5i, -3i, 5+5i, -4i, 5+5i, -5i, 5+5i, -6i, # same value, negative imaginary bases
5+5i, -2i, 5+5i, -3i, 5+5i, -4i, 5+5i, -5i, 5+5i, -6i, # same value, negative imaginary bases
Line 375: Line 374:
my $ibase = $v.&base($r);
my $ibase = $v.&base($r);
printf "%20s.&base\(%2si\) = %-10s : %12s.&parse-base\(%2si\) = %s\n",
printf "%20s.&base\(%2si\) = %-10s : %12s.&parse-base\(%2si\) = %s\n",
$v, $r.im, $ibase, "'$ibase'", $r.im, $ibase.&parse-base($r).round(1e-8);
$v, $r.im, $ibase, "'$ibase'", $r.im, $ibase.&parse-base($r).round(1e-8).narrow;
}</lang>
}</lang>
{{out}}
{{out}}
<pre> 0.&base( 2i) = 0 : '0'.&parse-base( 2i) = 0+0i
<pre> 0.&base( 2i) = 0 : '0'.&parse-base( 2i) = 0
1.&base( 2i) = 1 : '1'.&parse-base( 2i) = 1+0i
1.&base( 2i) = 1 : '1'.&parse-base( 2i) = 1
5.&base( 2i) = 10301 : '10301'.&parse-base( 2i) = 5+0i
5.&base( 2i) = 10301 : '10301'.&parse-base( 2i) = 5
-13.&base( 2i) = 1030003 : '1030003'.&parse-base( 2i) = -13+0i
-13.&base( 2i) = 1030003 : '1030003'.&parse-base( 2i) = -13
0+9i.&base( 2i) = 103010.2 : '103010.2'.&parse-base( 2i) = 0+9i
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
-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
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( 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( 3i) = 25.3 : '25.3'.&parse-base( 3i) = 5+5i

Revision as of 00:54, 13 February 2017

Imaginary base numbers is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

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 named.

Task: Write a set of procedures (functions, subroutines, however they are referred to in your language) to convert base 10 numbers to imaginary and back. At a minimum, support quater-imaginary (base 2i).

See Wikipedia: Quater-imaginary_base for more details.

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

Perl 6

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 since those bases will fit within standard base 36 digit representations. 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 here. Note that imaginary numbers are stored as floating point numbers in Perl 6 so some rounding error may creep in during calculations. The precision these conversion routines use is configurable; we are using 8 decimal places of precision here.

<lang perl6>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 $lower-bound = $radix / (-$radix + 1);
   my $upper-bound =      1 / (-$radix + 1);
   until $lower-bound <= $value < $upper-bound {
       $place += 1;
       $value = $num / $radix ** $place
   }
   while ($value or $place > 0) and $place > $precision {
       my $digit = ($radix * $value - $lower-bound).Int;
       $value = $value * $radix - $digit;
       $result ~= '.' unless $place or $result.contains: '.';
       $result ~= $digit == -$radix   ??
         ($digit-1).base(-$radix)~'0' !!
         $digit.base(-$radix);
       $place -= 1
   }
   $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;
   $re .= &base( -$radix.im².Int, :precision($precision) );
   $im = ($im/$radix.im).&base( -$radix.im².Int, :precision($precision) );
   my ($re-wh, $re-fr) = $re.split: '.';
   my ($im-wh, $im-fr) = $im.split: '.';
   $_ //=  for ($re-wh, $re-fr, $im-wh, $im-fr);
   my @whole = do given $re-wh.chars cmp $im-wh.chars {
       when Less { flat (flat $re-wh.flip.comb, '0' xx *) Z flat $im-wh.flip.comb }
       when More { flat $re-wh.flip.comb Z flat (flat $im-wh.flip.comb, '0' xx $re-wh.chars - $im-wh.chars) }
       default   { flat $re-wh.flip.comb Z flat $im-wh.flip.comb }
   }
   my @fraction = do given $re-fr.chars cmp $im-fr.chars {
       when More { flat (flat $im-fr.comb, '0' xx *) Z flat $re-fr.comb }
       when Less { flat $im-fr.comb Z flat (flat $re-fr.comb, '0' xx $im-fr.chars - $re-fr.chars) }
       default   { flat $im-fr.comb Z flat $re-fr.comb }
   }
   @whole.pop if +@whole and @whole.tail eq '0';
   @fraction.pop while +@fraction and @fraction.tail eq '0';
   +@fraction ??
   @whole.reverse.join ~ '.' ~ @fraction.join !!
   @whole.reverse.join

}

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 }

}

  1. 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
 -> $v, $r {

my $ibase = $v.&base($r); printf "%20s.&base\(%2si\) = %-10s : %12s.&parse-base\(%2si\) = %s\n",

 $v, $r.im, $ibase, "'$ibase'", $r.im, $ibase.&parse-base($r).round(1e-8).narrow;

}</lang>

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