Can I format an Erlang binary so that each byte is written in hex? I.e.,
> io:format(???, [<<255, 16>>]).
<<FF, 10>>
I don't see an obvious way to do it in io:format documentation, but perhaps I am simply missing one? Converting a binary to list and formatting its elements separately is too inefficient.
No, there is not such formating option but you can do something like:
io:format("<<~s>>~n", [[io_lib:format("~2.16.0B",[X]) || <<X:8>> <= <<255,16>> ]]).
There is a lot faster solution if you need.
-module(bin_to_hex).
-compile([native, {hipe, [o3]}]).
-export([bin_to_hex/1]).
bin_to_hex(B) when is_binary(B) ->
bin_to_hex(B, <<>>).
-define(H(X), (hex(X)):16).
bin_to_hex(<<>>, Acc) -> Acc;
bin_to_hex(Bin, Acc) when byte_size(Bin) band 7 =:= 0 ->
bin_to_hex_(Bin, Acc);
bin_to_hex(<<X:8, Rest/binary>>, Acc) ->
bin_to_hex(Rest, <<Acc/binary, ?H(X)>>).
bin_to_hex_(<<>>, Acc) -> Acc;
bin_to_hex_(<<A:8, B:8, C:8, D:8, E:8, F:8, G:8, H:8, Rest/binary>>, Acc) ->
bin_to_hex_(
Rest,
<<Acc/binary,
?H(A), ?H(B), ?H(C), ?H(D), ?H(E), ?H(F), ?H(G), ?H(H)>>).
-compile({inline, [hex/1]}).
hex(X) ->
element(
X+1, {16#3030, 16#3031, 16#3032, 16#3033, 16#3034, 16#3035, 16#3036,
16#3037, 16#3038, 16#3039, 16#3041, 16#3042, 16#3043, 16#3044,
16#3045, 16#3046, 16#3130, 16#3131, 16#3132, 16#3133, 16#3134,
16#3135, 16#3136, 16#3137, 16#3138, 16#3139, 16#3141, 16#3142,
16#3143, 16#3144, 16#3145, 16#3146, 16#3230, 16#3231, 16#3232,
16#3233, 16#3234, 16#3235, 16#3236, 16#3237, 16#3238, 16#3239,
16#3241, 16#3242, 16#3243, 16#3244, 16#3245, 16#3246, 16#3330,
16#3331, 16#3332, 16#3333, 16#3334, 16#3335, 16#3336, 16#3337,
16#3338, 16#3339, 16#3341, 16#3342, 16#3343, 16#3344, 16#3345,
16#3346, 16#3430, 16#3431, 16#3432, 16#3433, 16#3434, 16#3435,
16#3436, 16#3437, 16#3438, 16#3439, 16#3441, 16#3442, 16#3443,
16#3444, 16#3445, 16#3446, 16#3530, 16#3531, 16#3532, 16#3533,
16#3534, 16#3535, 16#3536, 16#3537, 16#3538, 16#3539, 16#3541,
16#3542, 16#3543, 16#3544, 16#3545, 16#3546, 16#3630, 16#3631,
16#3632, 16#3633, 16#3634, 16#3635, 16#3636, 16#3637, 16#3638,
16#3639, 16#3641, 16#3642, 16#3643, 16#3644, 16#3645, 16#3646,
16#3730, 16#3731, 16#3732, 16#3733, 16#3734, 16#3735, 16#3736,
16#3737, 16#3738, 16#3739, 16#3741, 16#3742, 16#3743, 16#3744,
16#3745, 16#3746, 16#3830, 16#3831, 16#3832, 16#3833, 16#3834,
16#3835, 16#3836, 16#3837, 16#3838, 16#3839, 16#3841, 16#3842,
16#3843, 16#3844, 16#3845, 16#3846, 16#3930, 16#3931, 16#3932,
16#3933, 16#3934, 16#3935, 16#3936, 16#3937, 16#3938, 16#3939,
16#3941, 16#3942, 16#3943, 16#3944, 16#3945, 16#3946, 16#4130,
16#4131, 16#4132, 16#4133, 16#4134, 16#4135, 16#4136, 16#4137,
16#4138, 16#4139, 16#4141, 16#4142, 16#4143, 16#4144, 16#4145,
16#4146, 16#4230, 16#4231, 16#4232, 16#4233, 16#4234, 16#4235,
16#4236, 16#4237, 16#4238, 16#4239, 16#4241, 16#4242, 16#4243,
16#4244, 16#4245, 16#4246, 16#4330, 16#4331, 16#4332, 16#4333,
16#4334, 16#4335, 16#4336, 16#4337, 16#4338, 16#4339, 16#4341,
16#4342, 16#4343, 16#4344, 16#4345, 16#4346, 16#4430, 16#4431,
16#4432, 16#4433, 16#4434, 16#4435, 16#4436, 16#4437, 16#4438,
16#4439, 16#4441, 16#4442, 16#4443, 16#4444, 16#4445, 16#4446,
16#4530, 16#4531, 16#4532, 16#4533, 16#4534, 16#4535, 16#4536,
16#4537, 16#4538, 16#4539, 16#4541, 16#4542, 16#4543, 16#4544,
16#4545, 16#4546, 16#4630, 16#4631, 16#4632, 16#4633, 16#4634,
16#4635, 16#4636, 16#4637, 16#4638, 16#4639, 16#4641, 16#4642,
16#4643, 16#4644, 16#4645, 16#4646}).
Which performs 90MB/s on mine notebook i5 CPU M 520 @ 2.40GHz when tested on 10MB chunks. But optimization was brought to the extreme there. It can also do 97MB if using 16bit lookup but it is crazy and too long to post here.
Improving upon @hairyhum
This takes care of zero paddings
<< <<Y>> ||<<X:4>> <= Id, Y <- integer_to_list(X,16)>>
reverse transformation
<<<<Z>> || <<X:8,Y:8>> <= Id,Z <- [binary_to_integer(<<X,Y>>,16)]>>, %%hex to binary
This hasn’t seen any action for a while, but all of the prior solutions seem overly convoluted. Here’s what, for me, seems much simpler:
[begin if N < 10 -> 48 + N; true -> 87 + N end end || <<N:4>> <= Bin]
If you prefer it expanded a bit:
[begin
if
N < 10 ->
48 + N; % 48 = $0
true ->
87 + N % 87 = ($a - 10)
end
end || <<N:4>> <= Bin]
You could do:
[ hd(erlang:integer_to_list(Nibble, 16)) || << Nibble:4 >> <= Binary ]
Which would return you a list(string) containing the hex digits of the binary. While I doubt the efficiency of this operation is going to have any effect on the runtime of your system, you could also have this bin_to_hex
function return an iolist
which is simpler to construct and will be flattened when output anyway. The following function returns an iolist
with the formatting example you gave:
bin_to_hex(Bin) when is_binary(Bin) ->
JoinableLength = byte_size(Bin) - 1,
<< Bytes:JoinableLength/binary, LastNibble1:4, LastNibble2:4 >> = Bin,
[ "<< ",
[ [ erlang:integer_to_list(Nibble1, 16), erlang:integer_to_list(Nibble2, 16), ", " ]
|| << Nibble1:4, Nibble2:4 >> <= Bytes ],
erlang:integer_to_list(LastNibble1, 16),
erlang:integer_to_list(LastNibble2, 16),
" >>" ].
It's a bit ugly, but runs through the binary once and doesn't traverse the output list (otherwise I'd have used string:join
to get the interspersed ", " sequences). If this function is not the inner loop of some process (I have a hard time believing this function will be your bottleneck), then you should probably go with some trivially less efficient, but far more obvious code like:
bin_to_hex(Bin) when is_binary(Bin) ->
"<< " ++ string:join([byte_to_hex(B) || << B >> <= Bin ],", ") ++ " >>".
byte_to_hex(<< N1:4, N2:4 >>) ->
[erlang:integer_to_list(N1, 16), erlang:integer_to_list(N2, 16)].
Here is another short and fast version which I use:
hexlify(Bin) when is_binary(Bin) ->
<< <<(hex(H)),(hex(L))>> || <<H:4,L:4>> <= Bin >>.
hex(C) when C < 10 -> $0 + C;
hex(C) -> $a + C - 10.
if you prefer to make a binary string instead of erlang default list strings, you may use binary comprehension syntax, like what I did on my sha1 generating code:
1> << << if N >= 10 -> N -10 + $a;
1> true -> N + $0 end >>
1> || <<N:4>> <= crypto:hash(sha, "hello world") >>.
<<"2aae6c35c94fcfb415dbe95f408b9ce91ee846ed">>
same as in python binascii.b2a_hex:
>>> binascii.b2a_hex(sha.new('hello world').digest())
'2aae6c35c94fcfb415dbe95f408b9ce91ee846ed'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With