18
\$\begingroup\$

The challenge

Make 2 programs which convert decimal to and from base 94, or a bidirectional program.

Base 94?

Base 94 is like any other base, but instead of 0 to 9 and letters, it uses ASCII characters 33 to 126. Not 127 because chr(127) is DEL. Not 32 because that's a space.

Examples

0 - ! or empty string
1 - "
93 - ~
94 - "!
2020 - 6O
14233221 - 2-n8

Scoring

For two programs, the score will be the sum of the byte count of both of the programs.

The bidirectional program is simply just the byte count. Consider it like a -50% to the score.

\$\endgroup\$
  • 1
    \$\begingroup\$ FWIW, base 94 is ok for encoding binary data but somewhat messy (and with a fair few wasted codes). 94^83 ~= 5.883e+163, 256^68 ~= 5.759e+163. Base 85 is neater. \$\endgroup\$ – PM 2Ring Apr 6 at 6:16
  • 2
    \$\begingroup\$ convert decimal to and from ... Wait what? So the input has to be a decimal string, not a normal int number? In most programming languages that define a base / internal representation for integer types, it's base 2 / binary. (For example, C guarantees that the object representation of integral type is binary, which determines how bit-shifts and bitwise booleans work). If you didn't mean base 10 input, perhaps you mean "serialize and deserialize a number to / from a base 94 string" or a less formal equivalent? Decimal, hex, and base 94 are serialization formats for numbers. \$\endgroup\$ – Peter Cordes Apr 6 at 10:03
  • 1
    \$\begingroup\$ @tsh: These are programming puzzles for programmers, and the rules are talking about the implementation, not end-use. The default I/O methods allow writing functions that take integer args like C void itoa_b94(char *out, int num), not only terminal read/print. But the wording in this question would rule that out and restrict you to taking input via stdin instead of a function arg (many more chars), or taking it as a decimal-float, or as some other decimal-based representation, because int has no implication of or association with decimal. \$\endgroup\$ – Peter Cordes Apr 8 at 1:28
  • 1
    \$\begingroup\$ @tsh: To put it another way, everyone (except you?) understands that this codegolf challenge is about converting between numbers and base94 strings. Not necessarily between decimal strings and base 94 strings. Many of the answers have a base94->number part that return a number in a numeric type like C int, x86 ISA dword, or JavaScript numeric, not print as decimal. People wrote those answers despite the question being worded to exclude them, if you read it literally. Related: this one is much more explicit about the number side, counting bytes \$\endgroup\$ – Peter Cordes Apr 8 at 2:07
  • 1
    \$\begingroup\$ Or just say "number"; it should be obvious to human readers that a no-op answer would be pointless. But maybe desirable to exclude bases that are easier to convert, like 94^2 which might be easy in the same way that 8-bit bytes naturally split into 2 hex digits. \$\endgroup\$ – Peter Cordes Apr 8 at 2:29

22 Answers 22

7
\$\begingroup\$

Haskell, 42 + 31 = 73 bytes

To base 94: 42 bytes

(l!!)
l="":tail[s++[c]|s<-l,c<-['!'..'~']]

Try it online!

Instead of using modular arithmetic, we generate the list l of all strings in the desired order and index into it.

The recursive definition of l is similar to this one for generating all strings, but modified to output strings in the desired order by appending the new character, and with a tail to prevent the analogue of leading zeroes.

From base 94: 31 bytes

foldl(\n->(94*n-33+).fromEnum)0

Try it online!

Iterates over the characters c of the string, updating the current value n to 94*n-33+fromEnum c.

| improve this answer | |
\$\endgroup\$
5
\$\begingroup\$

Python 2, 40 + 45 = 85 bytes

To base 94: 40 bytes

f=lambda n:n*"?"and f(n/94)+chr(n%94+33)

Try it online!

Python 3 would need 1 extra byte for // in place of /.

From base 94: 45 bytes

g=lambda s:s>''and g(s[:-1])*94+ord(s[-1])-33

Try it online!

Both to and from base 94: 86 bytes

h=lambda x:x*'?'and h(x/94)+chr(x%94+33)if x<''else x>''and h(x[:-1])*94+ord(x[-1])-33

Try it online!

A Frankenstein's monster stitched from the two above functions that turns out just barely longer than their byte total. Since this function accepts both numbers and strings, care must be taken not to produce an error caused by an illegal operation by acting on the wrong type.

| improve this answer | |
\$\endgroup\$
4
\$\begingroup\$

C (gcc), (34 + 44) = 78 bytes

The 'Decimal to Base94' function prints the Base94 number to STDOUT, whereas the 'Base94 to Decimal' function actually returns the integer.


Decimal to Base94:

f(n){n&&f(n/94)+putchar(n%94+33);}

Try it online!

Base94 to Decimal:

n;f(char*s){for(n=0;*s;n=n*94+*s++-33);n=n;}

Try it online!

| improve this answer | |
\$\endgroup\$
3
\$\begingroup\$

Bash + GNU utilities, Score 37 + 70 = 107 bytes


Base 10 to Base 94: 37 bytes

dc<<<94o$1p|sed 's/\b \|$/ 33+P/g'|dc

Try the test suite online!

Input is passed as an argument, and output is on stdout.

This works for inputs from \$0\$ through \$94^{23}-1.\$

This is probably sufficient, since \$94^{23}-1\$ is a huge number (46 decimal digits long — 2409576021839340871044919550282633620681129983 in base 10, or ~~~~~~~~~~~~~~~~~~~~~~~ in base 94), and this is already much larger than most languages can handle.


If you want it to work for arbitrarily large inputs, subject only to memory limitations, use

dc<<<94o$1p|sed 's/\\//g;s/\b \|$/ 33+P/g'|dc

instead (which is 45 bytes long).


Base 94 to Base 10: 78 70 bytes

n=$1;for((;${#n};)){ r=$[94*r+`printf %d "'$n"`-33];n=${n:1};};echo $r

Try the test suite online!

Input is passed as an argument, and output is on stdout.

This works for inputs up to \$2^{63}-1\$ (bash's maximum integer). That's 9223372036854775807 in base 10, or 1**0#VEx9D in base 94.

If you would like it to work for arbitrarily large inputs (subject to resource limitations), use

o()([ "$1" ]&&(printf %dz "'$1";o ${1:1}))
dc<<<`o $1|sed s/z/\ 33-+94*/g`94/p

(78 bytes long) instead.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ It seems you have forgotten to write a program to convert from base 94 back to base 10 \$\endgroup\$ – Lyxal Apr 4 at 7:41
  • \$\begingroup\$ @Lyxal It must be too late at night here -- thanks, I somehow missed that. \$\endgroup\$ – Mitchell Spector Apr 4 at 7:43
  • 1
    \$\begingroup\$ No worries! I originally missed that myself! \$\endgroup\$ – Lyxal Apr 4 at 7:43
  • \$\begingroup\$ For the first part, rs -T can help put each base-94 "digit" on its own line, which should shorten the sed part. For the second part, xxd with flags and then piping it to sed and then to dc should save some \$\endgroup\$ – user41805 Apr 4 at 16:41
2
\$\begingroup\$

05AB1E, Score 10 bytes

-4 bytes overall thanks to @KevinCruijssen

05AB1E, 5 bytes

žQ¦Åв

Try it online!

05AB1E, 5 bytes

žQ¦Åβ

Try it online!

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ -2 bytes in both programs: žQ¦Åв and žQ¦Åβ \$\endgroup\$ – Kevin Cruijssen Apr 4 at 9:55
  • \$\begingroup\$ Nice @KevinCruijssen I had toyed with žQ¦ but didn't find Åв or its counterpart \$\endgroup\$ – Expired Data Apr 4 at 9:58
  • 1
    \$\begingroup\$ You can search for 'custom base' to find them. ;) \$\endgroup\$ – Kevin Cruijssen Apr 4 at 9:59
2
\$\begingroup\$

W, 4 bytes

Encoding/Decoding program, it's bidirectional.

lpQB

Explanation

lp   % All printable characters, including the space.
  Q  % Extract all of the string except for the first item.
   B % Convert the input to/from base 94 using the custom base.
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ @PkmnQ I guess a bidirectional program is simply the bytecount, right? \$\endgroup\$ – Λ̸̸ Apr 4 at 11:03
  • \$\begingroup\$ Yes. I edited the rules. \$\endgroup\$ – PkmnQ Apr 4 at 12:59
2
\$\begingroup\$

Haskell, 44 43 + 32 = 75 bytes

-1 byte thanks to xnor.

Base 10 to base 94:

g 0=""
g n=g(div n 94)++[['!'..]!!mod n 94]

Try it online!

Base 94 to base 10:

foldl(#)0
n#c=n*94+fromEnum c-33

Try it online!

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ One byte shorter to index into a list of characters for the first one: Try it online! \$\endgroup\$ – xnor Apr 4 at 12:36
  • \$\begingroup\$ @xnor thanks a lot. I tried it with ['!'..'~'] but didn't consider using an infinite list of characters. \$\endgroup\$ – ovs Apr 4 at 12:43
  • \$\begingroup\$ A cute alternative there is cycle['!'..'~']!!n, but that's unfortunately longer. \$\endgroup\$ – xnor Apr 4 at 13:24
2
\$\begingroup\$

Japt, 4 bytes

Base-94 I/O as an array of characters. Output for 0 is an empty array, input for 0 can either be an empty array or ["!"].

;ìEÅ

Try it

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

C (gcc), 34 + 43 = 77 bytes

Encoder

f(n){n&&f(n/94)|putchar(n%94+33);}

Try it online!

Decoder

r;f(int*s){for(r=0;*s;)r=r*94+*s++-33;s=r;}

Takes a wide string as argument.

Try it online!

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Wolfram Language (Mathematica), 80 bytes

{FromCharacterCode[#~IntegerDigits~94+33]&,FromDigits[ToCharacterCode@#-33,94]&}

Try it online!

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Charcoal, 11 or 7 bytes

Base 10 to base 94: 5 bytes

⍘N✂γ¹

Try it online! Link is to verbose version of code. Takes input as a string of decimal digits optionally wrapped in []. Explanation:

 N      Cast input to number
⍘       Base conversion from custom base
   γ    Printable ASCII (including space)
  ✂ ¹   Slice off the first character
        Implicitly print

Base 94 to base 10: 6 bytes

I⍘S✂γ¹

Try it online! Link is to verbose version of code. Note: Due to the way Charcoal parses input, if the input looks like JSON then Charcoal will input it as JSON instead of a literal string. To avoid that, quote the input and wrap it in []s, e.g. to input [0] use ["[0]"]. Explanation:

  S     Cast input to string
 ⍘      Base conversion from custom base
    γ   Printable ASCII (including space)
   ✂ ¹  Slice off the first character
I       Cast from integer
        Implicitly print

Bidirectional: 7 bytes

⁺ω⍘A✂γ¹

Try it online! Link is to verbose version of code. Takes input as JSON wrapped in []s. Explanation:

   A    Input JSON element
  ⍘     Base conversion to or from custom base
     γ  Printable ASCII (including space)
    ✂ ¹ Slice off the first character
⁺ω      Concatenate empty string (casts result to string)
        Implicitly print
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ The bidirectional program is impressive, the 7 bytes is ok. I'm gonna edit the question. \$\endgroup\$ – PkmnQ Apr 4 at 10:15
  • \$\begingroup\$ First time I see a golfing language use a descriptive emoji! If only all golfing languages used them, I could actually read those programs without comments. Perhaps. \$\endgroup\$ – ComFreek Apr 5 at 14:53
  • \$\begingroup\$ @ComFreek Indeed, some of the functions do try to use somewhat related Unicode symbols, although I don't think there's a Unicode symbol for e.g. base conversion (I don't know why the developer picked that particular character). \$\endgroup\$ – Neil Apr 5 at 20:19
1
\$\begingroup\$

Python 2, 53 + 46 = 99 bytes

Base 10 to base 94:

n=input();r=''
while n:r=chr(n%94+33)+r;n/=94
print r

Try it online!

Base 94 to base 10:

lambda s:reduce(lambda a,b:a*94+ord(b)-33,s,0)

Try it online!

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Python 2, 47 + 51 = 98 40 + 42 = 82 bytes

Saved 12 bytes, inspired by @ovs 's suggestion.
Saved 1 byte by replacing len(s) with s>[], stolen from @xnor's Python answer.

f=lambda n:n*`n`and f(n/94)+chr(n%94+33)
g=lambda s:s>[]and ord(s.pop())-33+g(s)*94

Try it online!

f takes in an integer, and returns the base 94 string. Returns the empty string for 0.
g takes in a list of characters, and returns an integer. If input is a empty list (representing an empty string), False is returned in stead of 0, which is functionally equivalent in Python.

Python 2, 82 bytes

This is the bidirectional function that combines the previous 2 functions, with the same total byte count. Utilize the fact that in Python 2, 0 < positive int < empty list < non-empty list.

f=lambda n:n>[]and ord(n.pop())-33+f(n)*94or n<[]and n*`n`and f(n/94)+chr(n%94+33)

Try it online!

Input/output: If n is a list of characters, return the converted integer. If n is an integer, returns its base 94 string.

| improve this answer | |
\$\endgroup\$
  • 2
    \$\begingroup\$ You can do f without t: f=lambda n:n*[n]and f(n/94)+[chr(n%94+33)]. \$\endgroup\$ – ovs Apr 4 at 12:30
1
\$\begingroup\$

Ruby, 33 + 34 = 67 bytes

Encode (33):

f=->n{n>0?f[n/94]+""<<n%94+33:""}

Try it online!

Decode (34):

->s{i=0;s.bytes{|x|i=i*94+x-33};i}

Try it online!

Thanks Value Ink, as usual, for some byte trimming.

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

K (ngn/k), 9 + 10 = 19 bytes

To base 94, 9 bytes

`c$33+94\

Try it online!

From base 94, 10 bytes

94/-33+`i$

Try it online!

J, 15 + 13 = 28 bytes

To base 94, 15 bytes

[:u:33+94&#.inv

Try it online!

From base 94, 13 bytes

94#._33+a.i.]

Try it online!

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

x86 32-bit machine code, 14 + 17 = 31 bytes

atoi: explicit-length string input, pointer and length. Returns uint32_t
itoa: pointer to the end of a buffer, returns pointer to the first digit. Only works for non-negative int32_t - would cost 1 extra byte to xor edx,edx before div instead of cdq to sign-extend.

objdump -d -Mintel disassembly of both functions with machine code (the actual answer) and the source. Comments added manually.

    ;; input: char *RDI, size_t RCX
    ;; returns: unsigned EDX
    ;; clobbers: EAX temporary to load digits
08049000 <atoi_b94>:
 8049000:       31 c0                   xor    eax,eax               ; EAX = 0 so lodsb zero-extends to 32-bit
 8049002:       99                      cdq                          ; EDX = 0 = total
08049003 <atoi_b94.loop>:
 8049003:       ac                      lods   al,BYTE PTR ds:[esi]
 8049004:       6b d2 5e                imul   edx,edx,0x5e          ; total *= base
 8049007:       8d 54 02 df             lea    edx,[edx+eax*1-0x21]  ; total += char_to_int(digit)
 804900b:       e2 f6                   loop   8049003 <atoi_b94.loop>
 804900d:       c3                      ret    

You could call atoi from C if you declare the return value as a 128-bit integer, or a struct. itoa uses different calling convention.

    ;; input: unsigned EAX, char *RDI=end_ptr
    ;; result: converts into base94 in printing order in the buffer
    ;; returns: RDI=pointer to the first digit.  Caller knows where the end is because it passed that arg
    ;; clobbers: EDX
0804900e <itoa_end_base94>:
 804900e:       6a 5e                   push   0x5e
 8049010:       59                      pop    ecx      ; ECX = base
08049011 <itoa_end_base94.toascii_digit>:                         ; do{
 8049011:       99                      cdq    
 8049012:       f7 f1                   div    ecx                  ; EAX = quotient, EDX = remainder
 8049014:       83 c2 21                add    edx,0x21
 8049017:       4f                      dec    edi
 8049018:       88 17                   mov    BYTE PTR [edi],dl    ; *(--p) = digit
 804901a:       85 c0                   test   eax,eax            ; }while(x)
 804901c:       75 f3                   jne    8049011 <itoa_end_base94.toascii_digit>
 804901e:       c3                      ret    

next address is 0x1f, total size = 0x1f - 0 = 31 bytes.

Some of the comments use 64-bit registers; I switched to 32-bit to save 2 bytes in single-byte dec edi vs. dec rdi REX opcode modrm.

Try it online! with a test harness that converts an assemble-time-constant value to a string and back, prints the result, and checks for integer equality. Change the assembler command line option to -dTESTVAL=94 or whatever to test other values. If it prints the right string and exit status is 0, it worked.

I used FASM on TIO so I could make 32-bit code; TIO's will only assemble 64-bit object files with NASM/YASM, but FASM can output an executable directly, no linker involved.


For "normal" asm atoi and itoa, see my answers on those linked SO questions. Golfing them was straightforward, with only one significant change: use explicit-length strings for itoa so we can use loop instead of having to check for a terminator before tot = tot*base + digit. And turning itoa into a function that just does the conversion into the caller's buffer.

I also made the 94 and 33 assemble-time symbolic constants (in the TIO link)

I had hoped we might get some use out of stosb, but we need to go backwards, and I couldn't justify having atoi depend on DF=0 while having itoa use std without cld. Also, getting the remainder into AL means swapping with EDX, but then we need to swap back for the next div.

;; alternate worse version for after DIV
     std
     xchg   eax, edx
     add    al, ZERODIGIT        ; 2 bytes
     stosb                       ; *p-- = digit.  would also need a scasb or inc edi at the end to fix
     xchg   eax, edx
     cld

That's nearly break-even, IIRC one byte longer, but does *p-- = digit instead of the *--p = digit that we actually want. (Caller could change to pass a pointer to the last element of the buffer, instead of past the end, but I don't think we can justify returning a pointer to the uninitialized space below the first digit. So that would cost an extra inc edi after the loop.

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

PowerShell, 62 + 27 = 89 bytes

To Base 94:

param($a)for(;$a){$a-=$r=$a%94
$a/=94
$n=[char]($r+33)+$n}"$n"

Try it online!

From Base 94:

$args|%{$n=$n*94+$_-33}
+$n

Expects input via splatting.

Try it online!

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Julia 1.0, 40+46=86 bytes

To Base 64, 40 bytes

!x=reverse(join('!'.+digits(x,base=94)))

From Base 64, 46 bytes

~x=sum(@. 94^(length(x)-1:-1:0)*((x...,)-'!'))

Try it online!

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Your to and from base 94 is better, 40 + 46 = 86. \$\endgroup\$ – PkmnQ Apr 8 at 8:19
  • \$\begingroup\$ There is a semicolon or line break needed between the two though, so I thought that still counted \$\endgroup\$ – Simeon Schaub Apr 8 at 9:23
  • \$\begingroup\$ "For two programs, the score will be the sum of the byte count of both of the programs." \$\endgroup\$ – PkmnQ Apr 8 at 9:26
  • \$\begingroup\$ Ah, ok. I'll change that then \$\endgroup\$ – Simeon Schaub Apr 8 at 9:28
0
\$\begingroup\$

Python 3, 172 bytes

def t(n):return"0"if n<1 else t(n//94).lstrip("0")+''.join(map(chr,range(33,127)))[n%94]
def f(n):return ord(n)-33 if len(n)<2 else f(n[1:])+(ord(n[0])-33)*(94**(len(n)-1))

Try it online!

Based off this answer on StackOverflow

| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

Raku (raku -p) 60+42 Bytes = 102 Bytes

# convert base-10 to base-94
$!='';while $_ {$!=(chr $_%94+33)~$!;$_=$_.Int div 94};$_=$!

# convert base-94 to base-10
$!=0;$!=94*$!+$_-33 for $_.comb».ord;$_=$!
| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

Red, 68 + 58 = 126 bytes

To base 94, 68 bytes

f: func[n][head either n > 0[append f n / 94 #"!"+(n % 94)][copy""]]

Try it online!

From base 94, 58 bytes

g: func[s][either""= s[0][(-33 + take/last s)+(94 * g s)]]

Try it online!

| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

Perl 5, 36 32 + 26 23 = 55 bytes

To base 94: (Commandline options: -Minteger -n)

do{print chr$_%94+33}while$_/=94

Try it online!

From base 94: (Commandline Options: -pF)

map$\=$\*94-33+ord,@F}{

Try it online!

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.