[Erlang 0047] Erlang 進制轉換


    去年讀過的好書之一就是這本《編碼:隱匿在計算機軟硬件背后的語言》(豆瓣鏈接 )里面對進制有一段非常有意思的描述:
    如果人類像卡通人物那樣,每只手上只有 4個手指會怎樣呢?我們可能永遠都不會想到要發明一種以10為基礎的數字系統的問題, 取而代之的是我們可能會認為數字系統基於 8是正常、自然、合理、必然的,是毫無疑問的,是非常合適的。這時,就不能稱之為十進制了,得將它稱作為以8為基礎的數字系統或八進制。
... ...
龍蝦根本沒有手指,但它兩只前爪的末端都有螯。適合於龍蝦的數字系統是四進制數字系統或稱為基於4的數字系統.

 
 
   我們平常編程經常會遇到進制轉換的問題,有時候是為了把機器可處理的轉換成人類可讀的,有時候是把人類可讀的轉成機器可處理的;有時候是把一台機器可處理的轉成另外一台機器可以處理的;進制轉換幾乎是所有開發者的公共話題,這個問題也經常作為面試題出現......有中槍的么?
    
 在.net中,做進制轉換可以使用Convert.ToInt32(String,Base)做 2, 8, 10, 16進制之間的轉換:
     Convert.ToInt32("10101010",2);  Convert.ToInt32("12",16);
Convert.ToInt32 Method (String, Int32) 
The base of the number in  value , which must be 2, 8, 10, or 16. 
Converts the string representation of a number in a specified base to an equivalent 32-bit signed integer.

Erlang

 Erlang中進行進制轉換是比較方便的,首先在Shell中可以通過Base#Number的形式計算各種進制對應的十進制數值;
Eshell V5.9  (abort with ^G)
1> 2#1011.
11
2> 2#1111.
15
3> 16#12.
18
4> 16#1F.
31
5> 8#121.
81
6> 7#11.
8
7>
 Erlang BIF 提供了兩個基礎方法方便從十進制數值轉到各種進制字符串,以及其逆過程,目前支持的進制是2到36:
Eshell V5.9  (abort with ^G)
1> integer_to_list(1234,8).
"2322"
2> integer_to_list(1234,16).
"4D2"
3> list_to_integer("2322",8).
1234
4> list_to_integer("4d2",16).
1234

integer_to_list(Integer, Base) -> string()

Types:

Integer = integer()
Base = 2..36

Returns a string which corresponds to the text representation of Integer in base Base.

list_to_integer(String, Base) -> integer()

Types:

String = string()
Base = 2..36
Returns an integer whose text representation in base Base is String.

    群里面有人問如何把"abc"轉換成 "110000111000101100011"的形式,即逐字符轉成二進制,不管他轉成這樣的格式做什么用,實現起來還是比較簡單的:
lists:concat([erlang:integer_to_list(Item,2) || Item<-"abc"  ]).
 
這里看下從十進制到其它進制轉換是怎么實現的,這是一個通用的方法:
%%Code Path:\erl5.9\lib\erts-5.9\src\erlang.erl
integer_to_list(I0, Base, R0) ->
D = I0 rem Base,
I1 = I0 div Base,
R1 = if D >= 10 ->
[D-10+$A|R0];
true ->
[D+$0|R0]
end,
if I1 =:= 0 ->
R1;
true ->
integer_to_list(I1, Base, R1)
end.
   上面是一個通用的處理方法;對於一些特例的情況,比如十進制轉到16進制的情況,可以通過移位和邏輯運算完成:4bit的最大值是15 (2#1111) ,N band 15 獲得最后四位的值,不斷進行右移4位和band操作就可以完成10進制到16進制的轉換;
%% @spec hexdigit(integer()) -> char()
%% @doc Convert an integer less than 16 to a hex digit.
hexdigit(C) when C >= 0, C =< 9 ->
C + $0;
hexdigit(C) when C =< 15 ->
C + $a - 10.


to_hex_int(0, Acc) ->
Acc;
to_hex_int(I, Acc) ->
to_hex_int(I bsr 4, [hexdigit(I band 15) | Acc]).
  同樣的思路,可以完成10進制到2進制的轉換,以及10進制到8進制的轉換:
de2bin(0,Acc)->
Acc;
de2bin(N,Acc) ->
de2bin(N bsr 1,[((N band 1)+$0)|Acc]).

de2oct(0,Acc) ->
Acc;
de2oct(N,Acc) ->
de2oct(N bsr 3 ,[((N band 7)+$0)|Acc]).
 看下調用的效果:
Eshell V5.9  (abort with ^G)
1> zen:de2oct(1234,[]).
"2322"
2> 8#2322.
1234
3> zen:de2oct(1,[]).
"1"
4> zen:de2bin(1234,[]).
"10011010010"
5> list_to_integer(zen:de2oct(1234,[]),8).
1234
6>
Erlang開源項目mochiweb包含了一個處理十六進制數據的模塊,(下面的代碼我按照調用關系調整了一下):
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2006 Mochi Media, Inc.

%% @doc Utilities for working with hexadecimal strings.

-module(mochihex).
-author('bob@mochimedia.com').

-export([test/0, to_hex/1, to_bin/1, to_int/1, dehex/1, hexdigit/1]).

%% @type iolist() = [char() | binary() | iolist()]
%% @type iodata() = iolist() | binary()

%% @spec to_hex(integer | iolist()) -> string()
%% @doc Convert an iolist to a hexadecimal string.
to_hex(0) ->
"0";
to_hex(I) when is_integer(I), I > 0 ->
to_hex_int(I, []);
to_hex(B) ->
to_hex(iolist_to_binary(B), []).

to_hex(<<>>, Acc) ->
lists:reverse(Acc);
to_hex(<<C1:4, C2:4, Rest/binary>>, Acc) ->
to_hex(Rest, [hexdigit(C2), hexdigit(C1) | Acc]).


%% @spec hexdigit(integer()) -> char()
%% @doc Convert an integer less than 16 to a hex digit.
hexdigit(C) when C >= 0, C =< 9 ->
C + $0;
hexdigit(C) when C =< 15 ->
C + $a - 10.


to_hex_int(0, Acc) ->
Acc;
to_hex_int(I, Acc) ->
to_hex_int(I bsr 4, [hexdigit(I band 15) | Acc]).



%% @spec to_bin(string()) -> binary()
%% @doc Convert a hexadecimal string to a binary.
to_bin(L) ->
to_bin(L, []).

to_bin([], Acc) ->
iolist_to_binary(lists:reverse(Acc));
to_bin([C1, C2 | Rest], Acc) ->
to_bin(Rest, [(dehex(C1) bsl 4) bor dehex(C2) | Acc]).

%% @spec dehex(char()) -> integer()
%% @doc Convert a hex digit to its integer value.
dehex(C) when C >= $0, C =< $9 ->
C - $0;
dehex(C) when C >= $a, C =< $f ->
C - $a + 10;
dehex(C) when C >= $A, C =< $F ->
C - $A + 10.


%% @spec to_int(string()) -> integer()
%% @doc Convert a hexadecimal string to an integer.
to_int(L) ->
erlang:list_to_integer(L, 16).


%% @spec test() -> ok
%% @doc Test this module.
test() ->
"ff000ff1" = to_hex([255, 0, 15, 241]),
<<255, 0, 15, 241>> = to_bin("ff000ff1"),
16#ff000ff1 = to_int("ff000ff1"),
"ff000ff1" = to_hex(16#ff000ff1),
ok.
 二進制數據處理代碼段:
hexdigit(C) when C < 10 -> $0 + C;
hexdigit(C) when C < 16 -> $A + (C - 10).

unhexdigit(C) when C >= $0, C =< $9 -> C - $0;
unhexdigit(C) when C >= $a, C =< $f -> C - $a + 10;
unhexdigit(C) when C >= $A, C =< $F -> C - $A + 10.

quote_plus([], Acc) ->
    lists:reverse(Acc);
quote_plus([C | Rest], Acc) when ?QS_SAFE(C) ->
    quote_plus(Rest, [C | Acc]);
quote_plus([$\s | Rest], Acc) ->
    quote_plus(Rest, [$+ | Acc]);
quote_plus([C | Rest], Acc) ->
    <<Hi:4, Lo:4>> = <<C>>,
    quote_plus(Rest, [hexdigit(Lo), hexdigit(Hi), ?PERCENT | Acc]).

qs_revdecode(S) ->
    qs_revdecode(S, []).

qs_revdecode([], Acc) ->
    Acc;
qs_revdecode([$+ | Rest], Acc) ->
    qs_revdecode(Rest, [$\s | Acc]);
qs_revdecode([Lo, Hi, ?PERCENT | Rest], Acc) when ?IS_HEX(Lo), ?IS_HEX(Hi) ->
    qs_revdecode(Rest, [(unhexdigit(Lo) bor (unhexdigit(Hi) bsl 4)) | Acc]);
qs_revdecode([C | Rest], Acc) ->
    qs_revdecode(Rest, [C | Acc]).

 

 IP地址和整形互轉

還有一個常見的問題就是IP地址到整形的互相轉換,也可以按照同樣的思路如法炮制:
首先,我們先確定一些常量的值:
4> integer_to_list(16711680,2).  
"11111111,00000000,00000000"
6> integer_to_list(65280,2).   
"1111111100000000"
7> integer_to_list(16777216,2).
"1000000000000000000000000"
8> integer_to_list(65536,2).   
"10000000000000000"
9> integer_to_list(256,2).  
"100000000"
現在可以做轉換了:
10> Ip_to_integer=fun({A,B,C,D}) -> (A*16777216)+(B*65536)+(C*256)+D end.
#Fun<erl_eval.6.111823515>
11> Ip_to_integer({192,168,0,188}).
3232235708
12> Integer_to_ip=fun(Ip)-> {Ip bsr 24, (Ip band 16711680) bsr 16, (Ip band 65280) bsr 8, Ip band 255} end.
#Fun<erl_eval.6.111823515>
14> Integer_to_ip(3232235708).
{192,168,0,188}

 附:

bnot unary bitwise not integer
div integer division integer
rem integer remainder of X/Y integer
band bitwise and integer
bor bitwise or integer
bxor arithmetic bitwise xor integer
bsl arithmetic bitshift left integer
bsr bitshift right integer

 
P.S 有人問到列表解析的用法,這個可以看下官方文檔:http://www.erlang.org/doc/programming_examples/list_comprehensions.html
列表解析很強大的說,看下面的例子
sort([Pivot|T]) ->
sort([ X || X <- T, X < Pivot]) ++
[Pivot] ++
sort([ X || X <- T, X >= Pivot]);
sort([]) -> [].
上面是用兩行代碼實現了快速排序,在看一個實現全排列的
perms([]) -> [[]];
perms(L) -> [[H|T] || H <- L, T <- perms(L--[H])].
勾股定理數
pyth(N) ->
[ {A,B,C} ||
A <- lists:seq(1,N),
B <- lists:seq(1,N),
C <- lists:seq(1,N),
A+B+C =< N,
A*A+B*B == C*C
].

%%優化版本
pyth1(N) ->
[{A,B,C} ||
A <- lists:seq(1,N-2),
B <- lists:seq(A+1,N-1),
C <- lists:seq(B+1,N),
A+B+C =< N,
A*A+B*B == C*C ].
 
另外列表解析可以在Erlang Shell中方便的實現for循環和if
12> [io:format("abc")|| _<-lists:seq(1,10)]. 
abcabcabcabcabcabcabcabcabcabc[ok,ok,ok,ok,ok,ok,ok,ok,ok,ok]
13> E=34.
34
14> [io:format("abc")|| E>35].
[]
15> [io:format("abc")|| E>32].
abc[ok]
16>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM