2

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?

Chad
  • 2,365
  • 6
  • 26
  • 37
  • 2
    If you put 0 or 1 in, what should you get out? – Greg Oct 20 '09 at 07:36
  • 1
    I am confused. Going from base 10 to base 31 where you use the characters shown with translate 0 to '2', 1 to '3' and 2 to '4'. So a mapping of 2 to '4' is expected. Put another way, what do you expect tenTo31(0) or tenTo31(1) to output? – Paul Hsieh Oct 20 '09 at 07:37
  • 1
    What should tenTo31(1) return? – Igor ostrovsky Oct 20 '09 at 07:38
  • I'm aware that this function does not work. That is why I'm asking for help. I put the code up just to show what I had so far. I am NOT expecting this code to do what I'm asking. – Chad Oct 20 '09 at 07:39
  • Do any of you have suggestions on the right way to do this? – Chad Oct 20 '09 at 07:40
  • The right way to do what? Give a list of examples of what mappings you want and we can probably help you. – Paul Hsieh Oct 20 '09 at 07:41
  • Like this: http://www.crockford.com/wrmg/base32.html – Chad Oct 20 '09 at 07:43
  • That's a lossy encoding format. Are you sure you really want that? I mean if you input I, should it give you back 1 or issue an error? – Paul Hsieh Oct 20 '09 at 07:52
  • I'm still tying to find a way to reverse this. – Chad Oct 20 '09 at 10:02
  • I've decided to use VolkerK's method, which allows me to decode as well as encode. – Chad Oct 22 '09 at 20:37

6 Answers6

4

This is a blind guess at what you want:

$alpha = "yz23456789abcdefghjkmnpqrstuvwx";
Greg
  • 316,276
  • 54
  • 369
  • 333
  • Clever -- at least it will work for the inputs 2 through 9. :) – Paul Hsieh Oct 20 '09 at 07:44
  • Probably also the line recalculating `$num` should not have the `- 1` at the end. – dave4420 Oct 20 '09 at 07:48
  • Thank Greg, lucky guess :D Also, thanks to everyone for your input and ideas. – Chad Oct 20 '09 at 08:02
  • 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 Oct 20 '09 at 08:40
  • You're getting intger overflow because your number is > 2^31. Try echo (int)398985999599; - see it's negative. – Greg Oct 20 '09 at 10:28
4

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
  • 95,432
  • 20
  • 163
  • 226
  • 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 yy whereas your function tenTo31(31) gives me zy – Chad Oct 20 '09 at 08:45
  • 1
    `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 Oct 20 '09 at 09:31
  • Ok, one more thing, how do i convert the output back to decimal? – Chad Oct 20 '09 at 09:36
  • I'm referring to your function, which I plan to use. – Chad Oct 20 '09 at 09:39
  • Also, please make a very small edit to this post so that I can vote. – Chad Oct 20 '09 at 10:20
2

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
  • 2,369
  • 18
  • 16
  • To put it another way if you eliminated the characters abcde it would work the way you expect it to. – Tim Oct 20 '09 at 07:21
  • 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 Oct 20 '09 at 07:29
  • 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 Oct 20 '09 at 07:38
1

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
  • 1,158
  • 2
  • 11
  • 23
0

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.

Alana Storm
  • 164,128
  • 91
  • 395
  • 599
0

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
  • 1,466
  • 7
  • 9