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

• 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. – PM 2Ring Apr 6 at 6:16
• 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. – Peter Cordes Apr 6 at 10:03
• @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. – Peter Cordes Apr 8 at 1:28
• @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 – Peter Cordes Apr 8 at 2:07
• 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. – Peter Cordes Apr 8 at 2:29

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

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

# 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!

# 05AB1E, Score 10 bytes

-4 bytes overall thanks to @KevinCruijssen

## 05AB1E, 5 bytes

žQ¦Åв


Try it online!

# 05AB1E, 5 bytes

žQ¦Åβ


Try it online!

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

• @PkmnQ I guess a bidirectional program is simply the bytecount, right? – user92069 Apr 4 at 11:03
• Yes. I edited the rules. – PkmnQ Apr 4 at 12:59

# 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


Try it online!

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

# 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

# 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*/g94/p


• It seems you have forgotten to write a program to convert from base 94 back to base 10 – Lyxal Apr 4 at 7:41
• @Lyxal It must be too late at night here -- thanks, I somehow missed that. – Mitchell Spector Apr 4 at 7:43
• No worries! I originally missed that myself! – Lyxal Apr 4 at 7:43
• 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 – user41805 Apr 4 at 16:41

# 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!

# Wolfram Language (Mathematica), 80 bytes

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


Try it online!

# Charcoal, 11 or 7 bytes

## Base 10 to base 94: 5 bytes

⍘Ｎ✂γ¹


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

 Ｎ      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

Ｉ⍘Ｓ✂γ¹


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  use [""]. Explanation:

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


## Bidirectional: 7 bytes

⁺ω⍘Ａ✂γ¹


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

   Ａ    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

• The bidirectional program is impressive, the 7 bytes is ok. I'm gonna edit the question. – PkmnQ Apr 4 at 10:15
• 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. – ComFreek Apr 5 at 14:53
• @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). – Neil Apr 5 at 20:19

# 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!

# 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*nand 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*nand 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.

• You can do f without t: f=lambda n:n*[n]and f(n/94)+[chr(n%94+33)]. – ovs Apr 4 at 12:30

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

c$33+94\  Try it online! ## From base 94, 10 bytes 94/-33+i$


Try it online!

## To base 94, 15 bytes

[:u:33+94&#.inv


Try it online!

## From base 94, 13 bytes

94#._33+a.i.]


Try it online!

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

# 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;$_=$!


# 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[(-33 + take/last s)+(94 * g s)]]


Try it online!

# 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!

# 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!

# 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!

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

# 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)-33)*(94**(len(n)-1))


Try it online!

Based off this answer on StackOverflow