Binary to integer (no parsing)

Hi,

Is there a function that would convert <<1>> to 1, <<1, 0>> to 256. I do not mean parsing the binary as a string representation of the integer.

I want to know how bing a 20 bytes integer can be.

b = 1..20 |> Enum.map(fn _ -> 255 end) |> :erlang.list_to_binary
<<255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  255, 255, 255, 255, 255>>

Thank you.

1 Like

If you’re just doing one number, you can use pattern matching likr this:

<<number::16>> = <<1, 0>>

And that will give you 256. This strategy will work for a 20 byte integer (obviously not with 16). Though if you’re using it as a literal I recommend number=0xFFFFFFFF....FFFFF

1 Like
iex(2)> <<int::integer-size(2)-unit(8)>> = <<255, 255>>
<<255, 255>>
iex(3)> int
65535
3 Likes

Not builtin, you have to implement that on your own. Though thats not an easy task, as there are meany questions you didn’t even ask, like endianess or signedness.

20 bytes are 160 bits. Setting them all to 1 is 2^160 - 1 in unsigned interpretation.

Actually I wanted to know the number of possible values, sorry for the XY question. But I am still interested in writing such function.

Say unsigned big endian. I came up with that. It seems to work, but I find it unsatisfying:


expected = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
^expected = 1_461_501_637_330_902_918_203_684_832_716_283_019_655_932_542_975

defmodule T do
  def to_int(bin, endianness \\ :big) do
    list =
      bin
      |> :erlang.binary_to_list()

    case endianness do
      :big -> :lists.reverse(list)
      :little -> list
    end
    |> Enum.with_index()
    |> Enum.reduce(0, fn {byte, i}, acc ->
      acc + byte * round(:math.pow(256, i))
    end)
  end
end

256 = T.to_int(<<1, 0>>)

^expected =
  T.to_int(
    <<255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
      255, 255>>
  )

System.halt()

(edit: I guess with signed ints we could abuse the binary_to_term function by adding <<131,98>> in front and making sure the binary length is a multiple of 4 bytes …)

There are 2^n possible values in n bits.

3 Likes
fn bin ->
  size = :erlang.size(bin) * 8
  <<n::size(size)>> = bin
  n
end

If you want to play with endianness and what not there are binary pattern matching options you can shove into that match.

Try :binary.decode_unsigned/1,2, it does exactly what your code does…

9 Likes

that function is kind of overcomplicated, consider (untested):

def to_int (<<>>, acc \\ 0), do: acc
def to_int (<<byte, rest::binary>>, acc \\ 0), do: to_int(rest, acc * 256 + byte)

little-endian left as an exercise… :wink:

Thank you all :slight_smile:

Your local thread necromancer checking in.

I am gonna work in my free time on a fairly basic network protocol in Rust, Golang and Elixir, and I am starting with Elixir.

I need to be able to encode and decode unsigned 32-bit integers to/from byte buffers (big endian). While :binary.encode_unsigned(x, :big) works the protocol requires all 4 bytes be sent, and this function always finds the smallest possible representation:

iex(1)> :binary.encode_unsigned 0xFF, :big
<<255>>

Whereas I need <<0, 0, 0, 255>>.

So just to bring visibility in case someone ever needs, encoding is as simple as:

iex(1)> <<255::unsigned-big-integer-32>>
<<0, 0, 0, 255>>

And decoding is done like so:

iex(1)> :binary.decode_unsigned(<<0, 0, 0, 255>>, :big)
255

(Taken from the docs of <<>> and :binary.)

1 Like

I’d suggest the inverse from encoding, to keep things mirrored/simple:

iex(1)> <<int::unsigned-big-integer-32>> = <<0, 0, 0, 255>>
<<0, 0, 0, 255>>
iex(2)> int
255
1 Like

Actually yeah, I was just about to post an edit, thank you. :+1:

Guess I really wanted it to be a one-liner but that’s not an important consideration.