Save octal & binary in varible

Hi
code in Python

input = "Input1337"
encode = input.encode("utf-8")
hex = encode.hex()
oct = oct(int(hex, 16))
binary = " ".join(f"{ord(i):08b}" for i in input)

print("input  :", input)
print("octal  :", oct)
print("binary :", binary)

out:

input  : Input1337
octal  : 0o222671603527206114631467
binary : 01001001 01101110 01110000 01110101 01110100 00110001 00110011 00110011 00110111

in elixir:

input = "Input1337"
hex = Base.encode16(input)
IO.puts("Input  : #{input}")
IO.inspect(hex, base: :binary)
IO.inspect(hex, base: :octal)

out:

Input  : Input1337
<<0b1001001, 0b1101110, 0b1110000, 0b1110101, 0b1110100, 0b110001, 0b110011,
  0b110011, 0b110111>>
<<0o111, 0o156, 0o160, 0o165, 0o164, 0o61, 0o63, 0o63, 0o67>>

I was only able to convert using IO.inspect
But I feel that I have made a mistake again
How can I save it in a variable like the Python code above?

Thanks

I’m not sure what you’re trying to achieve here since binary, hex and octal are just different representations of the string, not necessarily “conversions”. I think you are looking to store the binary (Elixir string) representations of input formatted as hex and octal. In which case inspect/2 may be what you are after:

iex> input = "Input1337"                                                 
"Input1337"
iex> binary = inspect(input, base: :binary)
"<<0b1001001, 0b1101110, 0b1110000, 0b1110101, 0b1110100, 0b110001, 0b110011, 0b110011, 0b110111>>"
iex> hex = inspect(input, base: :hex)      
"<<0x49, 0x6E, 0x70, 0x75, 0x74, 0x31, 0x33, 0x33, 0x37>>"
iex> octal = inspect(input, base: :octal)
"<<0o111, 0o156, 0o160, 0o165, 0o164, 0o61, 0o63, 0o63, 0o67>>"

Note that the output is itself executable code showing different representations of input:

iex> <<0b1001001, 0b1101110, 0b1110000, 0b1110101, 0b1110100, 0b110001, 0b110011, 0b110011, 0b110111>>
"Input1337"
iex> <<0x49, 0x6E, 0x70, 0x75, 0x74, 0x31, 0x33, 0x33, 0x37>>
"Input1337"
iex> <<0o111, 0o156, 0o160, 0o165, 0o164, 0o61, 0o63, 0o63, 0o67>>
"Input1337"
5 Likes

That’s right, thank you

Do you have a solution to display as below?

<<0b1001001, 0b1101110, 0b1110000, 0b1110101, 0b1110100, 0b110001, 0b110011,
  0b110011, 0b110111>>

to:

010010010110111001110000011101010111010000110001001100110011001100110111

thanks

What I mean is to out of bitstrings mode and display as string

This should do the trick:

for << byte :: unsigned-integer-8 <- "Input1337" >>, reduce: [] do
  acc -> 
    bin = Integer.to_string(byte, 2)
    [String.duplicate("0", 8 - String.length(bin)) <> bin | acc] 
end
|> Enum.reverse()
|> Enum.join()

Explanation

  • decompose the string into 8 bit unsigned integers
  • for each integer, print it as a base 2 string
  • pad the string to 8 digits
  • reverse the list of string (because we were inserted each byte at the head of the list
  • join the strings together
2 Likes

Here’s another version that works just on binaries, but building binaries might not be as efficient as working with lists (not checked):

for << byte :: unsigned-integer-8 <- "Input1337" >>, reduce: <<>> do
  acc -> 
    bin = Integer.to_string(byte, 2)
    acc <> String.duplicate("0", 8 - String.length(bin)) <> bin
end
2 Likes

very good
thank you

Just for fun, here’s one more that I think it more expressive:

for << bit :: 1 <- "Input1337" >>, reduce: <<>> do
  acc -> acc <> Integer.to_string(bit)
end

Performance

After benchmarking, this implementation is the most performant (by between 12% and 17% percent over the others) at the expense of about 30% higher memory usage. 10kb versus 7.5kb for the original string in this post.

2 Likes

How about this?

for <<byte <- "Input1337">>, into: "", do: Integer.to_string(byte, 2)

Benchmark:

Name           ips        average  deviation         median         99th %
adam        1.88 M        0.53 μs  ±5159.76%        0.46 μs        0.63 μs
kip         0.30 M        3.31 μs   ±332.77%        3.08 μs        7.08 μs

Comparison:
adam        1.88 M
kip         0.30 M - 6.23x slower +2.78 μs

Operating System: macOS
CPU Information: Apple M1 Pro
Number of Available Cores: 10
Available memory: 16 GB
Elixir 1.13.4
Erlang 25.0.2

Edit: the above is buggy because it doesn’t pad the strings with leading zeros. Correct but slower version:

    for <<byte <- input>>, into: "" do
      byte |> Integer.to_string(2) |> String.pad_leading(8, "0")
    end
4 Likes

Oh, yes, much better! Great work.

1 Like

Here’s a generic version that works for all bases.

def to_base(binary, base) do
  bits = :math.log2(base) |> round()
  for <<nibble::size(bits) <- binary>>, into: "", do: <<?0 + nibble>>
end
Name              ips        average  deviation         median         99th %
adam           1.94 M      514.63 ns  ±3751.97%         458 ns         625 ns
to_base        1.09 M      913.44 ns  ±2482.01%         833 ns        1000 ns
kip            0.30 M     3310.78 ns   ±369.75%        3083 ns        7084 ns

Comparison:
adam           1.94 M
to_base        1.09 M - 1.77x slower +398.82 ns
kip            0.30 M - 6.43x slower +2796.15 ns

Edit: Actually only works up to base 10 because after that there aren’t enough decimal digits to display the serialized value, so might be better avoid calculating the bit size and hardcode it:

binary: for <<bit::1 <- binary>>, into: "", do: <<?0 + bit>>
octal: for <<nibble::3 <- binary>>, into: "", do: <<?0 + nibble>>

Edit 2: Also only works properly when the base is a power of 2 :smile:

Edit 3: hmm something is wrong because the results differ between my two versions. Will come back to this later!

4 Likes

Thanks

Back again :smile:

There was an error in my first reply: I forgot to pad the integers with 0s. Adding that brings it to about the same performance as Kip’s suggestion.

But the above suggestion is still about 4 times faster on my machine.

TLDR:

def to_binary(input) do
  for <<bit::1 <- input>>, into: "", do: <<?0 + bit>>
end

def to_octal(input) do
  for <<nibble::3 <- input>>, into: "", do: <<?0 + nibble>>
end
3 Likes

I tried all methods
And I found a new way

hex = Base.encode16(input, case: :lower)
{decimal, _} = Integer.parse(hex, 16)
binary = Integer.to_string(decimal, 2)
octal = Integer.to_string(decimal, 8)