views:

295

answers:

6

I'd like to convert base 10 numbers to base 31

I would like to use only these characters: 23456789abcdefghjkmnpqrstuvwxyz

As you can see, 5 characters are excluded (i don't need these): 1 0 o l i

The function I have now is below but of course it doesn't work. When 2 is input it outputs 4. The output for tenTo31(2) should be 2

function tenTo31($num)
{
    $out   = "";
    $alpha = "23456789abcdefghjkmnpqrstuvwxyz";

    while($num > 30)
    {
        $r = $num % 31;
        $num = floor($num / 31) - 1;
        $out = $alpha[$r] . $out;
    }

    return $alpha[$num] . $out;
}

Any ideas on how to make this work?

+2  A: 

You aren't using the 1 and 0 characters, the first digit in your numbering system is 2 meaning 2 is the equivalent of 0 in base 10. 3 is equivalent to 1 in base 10 and 4 is equivalent to 2 in base 10.

Tim
To put it another way if you eliminated the characters abcde it would work the way you expect it to.
Tim
Yes, I understand. That is why I am posting here because I can't figure out how to make the function work as I wanted it to.
Chad
You are getting the second value in your counting system.Imagine you didn't use numbers at all, you just used abcdef so if you put in 2, you would expect to get c 0=a,1=b,2=c Ok, now lets replace abcdef with what you used as a string 23456 0=2,1=3,2=4 That's the problem, its with the way you started your number system.
Tim
+1  A: 

Why are you taking modules by 32? You should use %31 and /31. In base 10 we are using modules by 10, so should be in base 31. But if we forget about this, I think your logic is correct. I can't understand why 2 in base 10 is equal to 4 in base 31 using your "modified digits".

giolekva
+4  A: 

This is a blind guess at what you want:

$alpha = "yz23456789abcdefghjkmnpqrstuvwx";
Greg
Clever -- at least it will work for the inputs 2 through 9. :)
Paul Hsieh
Probably also the line recalculating `$num` should not have the `- 1` at the end.
Dave Hinton
@Paul Hsieh,It seems to be working for inputs larger than 9
Chad
Thank Greg, lucky guess :DAlso, thanks to everyone for your input and ideas.
Chad
When I use this with my original script and feed it a very number, I get an error like this:Notice: Uninitialized string offset: -6 in C:\wamp\www\test\functions.php on line 40
Chad
The above error appears when run tenTo31(398985999599);
Chad
You're getting intger overflow because your number is > 2^31. Try echo (int)398985999599; - see it's negative.
Greg
+2  A: 

There's a built-in function for converting from one base to another, base_convert(). The alphabet is fixed, but you can use strtr() to replace those digits with your own.

"The output for tenTo31(2) should be 2": One possibility is to make '2' the third symbol again.

function tenTo31($num) {
  static $from = "0123456789abcdefghijklmnopqrstu";
  static $to   = "yz23456789abcdefghjkmnpqrstuvwx";
  return strtr(base_convert($num, 10, 31), $from, $to);
}

for($i=0; $i<31; $i++) {
 echo $i, '=', tenTo31($i), ' | ';
 if ( 9===$i%10 ) echo "\n";
}

prints

0=y | 1=z | 2=2 | 3=3 | 4=4 | 5=5 | 6=6 | 7=7 | 8=8 | 9=9 | 
10=a | 11=b | 12=c | 13=d | 14=e | 15=f | 16=g | 17=h | 18=j | 19=k | 
20=m | 21=n | 22=p | 23=q | 24=r | 25=s | 26=t | 27=u | 28=v | 29=w | 
30=x |

edit: To convert the base(31) number back to decimal you first have to reverse the translation (strtr) and then call base_convert(.., 31, 10). You can combine the conversion from and to base(31) in a single function.

function convert_ten_31($num, $numIsDecimal) {
  static $default = "0123456789abcdefghijklmnopqrstu";
  static $symbols = "yz23456789abcdefghjkmnpqrstuvwx";

  if ( $numIsDecimal ) {
   return strtr(base_convert($num, 10, 31), $default, $symbols);
  }
  else {
   return base_convert(strtr($num, $symbols, $default), 31, 10);
  } 
}

// testing
for($i=0; $i<10000; $i++) {
 $x = convert_ten_31($i, true);
 $x = convert_ten_31($x, false);

 if ( $i!==(int)$x ) {
  var_dump($i, $x);
  die;
 }
}
echo 'done.';

It's also easily possible to write a function like base_convert() yourself that take the symbols as parameter and thus having one flexible function instead of tenTo30(), tenTo31(), tenTo32(), ....

VolkerK
This is good but is there a way to make it behave more like my original function?In my original function: tenTo31(31) give me yywhereas your function tenTo31(31) gives me zy
Chad
`zy` is correct. 31(dec) is 10(31), translated to your alphabet (as suggested in this thread) `zy`. `yy` would be `00`. The -1 after floor(..) in your function needs to be removed.
VolkerK
Ok, one more thing, how do i convert the output back to decimal?
Chad
I'm referring to your function, which I plan to use.
Chad
from/to conversion added.
VolkerK
Perfect. Thank you.
Chad
Also, please make a very small edit to this post so that I can vote.
Chad
A: 

While I'd encourage you to continue along with your algorithm for the learning exercise, consider using base_convert if you just need to get the job done.

Alan Storm
A: 

The mapping according to http://www.crockford.com/wrmg/base32.html appears to be:

function symbolToEncode ($num) {
    $out   = "";
    static $alpha = "0123456789ABCDEFGHJKMNPQRSTVWXYZ*~$=U";

    while ($num >= 37) {
        $r = $num % 37;
        $num = floor ($num / 37);
        $out = $out . $alpha[$r];
    }

    return $out . $alpha[$num];
}

function decodeToEncode ($str) {
  static $from = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ*~=$";
  static $to   = "0123456789ABCDEFGH1JK1MN0PQRSTUVWXYZABCDEFGH1JK1MN0PQRSTUVWXYZ*~=$";
  return strtr ($str, $from, $to);
}

Though clearly the real challenge is to write a encodeToSymbol() function. I am not really a PHP expert (my $'s in the strings probably needs to be escaped somehow -- hints?), so I will leave that to others.

Paul Hsieh