Erlang Central

Difference between revisions of "Commas in Numbers"

From ErlangCentral Wiki

(Solution)
(2 intermediate revisions by 2 users not shown)
Line 5: Line 5:
 
== Solution ==
 
== Solution ==
  
Use the Perl-compatible regular expression engine to insert the commas where needed. It's simplest to reverse the string prior to the regexp-pass so that we can hop over any decimal portion. Then reverse the string back on return.
+
Turn the number into a list and then use pattern-matching to insert commas where needed.
 +
 
 
<code>
 
<code>
partition_number(Number, Separator) ->
+
partition_integer(N,P) ->
     SepChar = hd(Separator),
+
     partition_integer(lists:reverse(integer_to_list(N)),P,[]).
    NumStr = if
+
 
      is_integer(Number) -> hd(io_lib:format("~B", [Number]));
+
partition_integer([A,B,C,D|T],P,Acc) ->
      is_float(Number) -> hd(io_lib:format("~f", [Number]));
+
     partition_integer([D|T],P,[P,C,B,A|Acc]);
      true -> Number
+
partition_integer(L,_,Acc) ->
    end,
+
     lists:reverse(L) ++ Acc.
    % If there is a decimal point, use that as the starting point
+
    {ChgStr, Remain} = case regexp:first_match(NumStr, "\\.") of
+
        {match,Start,Length} ->
+
      {string:substr(NumStr, 1, Start - 1),
+
                string:substr(NumStr,Start,string:len(NumStr))};
+
  nomatch -> {NumStr, ""}
+
     end,
+
    {ok, Subs, _} = regexp:gsub(lists:reverse(ChgStr),
+
      "([0-9][0-9][0-9])", "&" ++ Separator),
+
    Final = case lists:nth(string:len(Subs), Subs) of
+
        SepChar -> string:substr(Subs, 1, string:len(Subs) - 1);
+
        _      -> Subs
+
    end,
+
     lists:reverse(Final) ++ Remain.
+
 
</code>
 
</code>
  
While this function works, it would be better if it checked for someone passing the separator parameter as a character (rather than a string).
+
Floating-point numbers generally require additional separator, and also an explicit fractional-part length:
  
Here's an example of partition_number in action:
 
 
<code>
 
<code>
1> partition_number(1918928282, ",").
+
partition_float(N,P,FP,L) ->
 +
    F = tl(tl(hd(io_lib:format("~.*f",[L,N-trunc(N)])))),
 +
    lists:flatten([partition_integer(trunc(N),P),FP,F]).
 +
</code>
 +
 
 +
Here's an example of the above in action:
 +
<code>
 +
1> partition_integer(1918928282, $,).
 
"1,918,928,282"
 
"1,918,928,282"
2> partition_number(1982928828, "'").  
+
2> partition_integer(1982928828, $').  
 
"1'982'928'828"
 
"1'982'928'828"
3> partition_number(1982928828.12345, "'").
+
3> partition_float(1982928828.12345, $', $., 6).
 
"1'982'928'828.123450"
 
"1'982'928'828.123450"
</code>
+
</code>  
 
+
In addition to Erlang's various other string-related ills (mainly poor memory and time performance), it has a very limited set of regular expressions. For example, our implementation of partition_number is over twice as long as the [[http://schemecookbook.org/view/Cookbook/NumberRecipeCommas][Scheme alternative] because it has no look-ahead (or negative look-ahead) searching functionality.
+
  
 
[[Category:CookBook]][[Category:StringRecipes]][[Category:ListRecipes]][[Category:NumberRecipes]]
 
[[Category:CookBook]][[Category:StringRecipes]][[Category:ListRecipes]][[Category:NumberRecipes]]

Revision as of 09:47, 23 November 2006

Problem

You need to format a number for printing with commas (or other locale-dependant marks) in the appropriate places. This is important for creating human-legible print copies of reports.

Solution

Turn the number into a list and then use pattern-matching to insert commas where needed.

partition_integer(N,P) ->
    partition_integer(lists:reverse(integer_to_list(N)),P,[]).

partition_integer([A,B,C,D|T],P,Acc) ->
    partition_integer([D|T],P,[P,C,B,A|Acc]);
partition_integer(L,_,Acc) ->
    lists:reverse(L) ++ Acc.

Floating-point numbers generally require additional separator, and also an explicit fractional-part length:

partition_float(N,P,FP,L) ->
    F = tl(tl(hd(io_lib:format("~.*f",[L,N-trunc(N)])))),
    lists:flatten([partition_integer(trunc(N),P),FP,F]).

Here's an example of the above in action:

1> partition_integer(1918928282, $,).
"1,918,928,282"
2> partition_integer(1982928828, $'). 
"1'982'928'828"
3> partition_float(1982928828.12345, $', $., 6).
"1'982'928'828.123450"