Localization 如何使用Erlang将数字格式化为带有千和十进制分隔符的字符串(货币)?

Localization 如何使用Erlang将数字格式化为带有千和十进制分隔符的字符串(货币)?,localization,formatting,erlang,currency,number-formatting,Localization,Formatting,Erlang,Currency,Number Formatting,我正在用Erlang开发一个应用程序,它必须处理几种不同货币的数字格式。需要考虑的复杂性有很多,比如货币符号,它的位置(右或左),等等。但是这种格式的一个方面在其他编程语言中是直接的,但是在Erlang中很难做到,Erlang是用于千位和小数位数的分隔符 对于大多数英语国家(独立于货币),预期数字格式为: 9,999,999.99 对于其他几个国家,如德国和巴西,数字格式不同: 9.999.999,99 还有其他变体,如法语变体,带有空格: 9 999 999,99 问题是我找不到一个很好

我正在用Erlang开发一个应用程序,它必须处理几种不同货币的数字格式。需要考虑的复杂性有很多,比如货币符号,它的位置(右或左),等等。但是这种格式的一个方面在其他编程语言中是直接的,但是在Erlang中很难做到,Erlang是用于千位和小数位数的分隔符

对于大多数英语国家(独立于货币),预期数字格式为:

9,999,999.99
对于其他几个国家,如德国和巴西,数字格式不同:

9.999.999,99
还有其他变体,如法语变体,带有空格:

9 999 999,99
问题是我找不到一个很好的方法来实现这些格式,从浮点数开始。当然,
io_lib:format/2
可以将其转换为字符串,但它似乎不提供对用作十进制分隔符的符号的任何控制,也不输出任何千位分隔符(这使得“搜索和替换”的解决方案不可能)

这是我们迄今为止的一个例子:

%% currency_l10n.erl

-module(currency_l10n).
-export([format/2]).

number_format(us, Value) ->
    io_lib:format("~.2f", [Value]);

number_format(de, Value) ->
    io_lib:format("~B,~2..0B", [trunc(Value), trunc(Value*100) rem 100]).


%% Examples on the Erlang REPL:

1> currency_l10n:number_format(us, 9999.99).
"9999.99"
2> currency_l10n:number_format(de, 9999.99).
"9999,99"

正如您所看到的,十进制分隔符的解决方案已经不是很好了,处理数千的分隔符也不会更好。这里缺少什么吗?

我想您可以使用
re:replace/4
例如,对于DE:

1>re:replace(“9999.99”、“\\”、“,”、“,[global,{return,list}])。
"9999,99"
但是,为了像在法语中一样在最后两个字符中添加逗号,我想您可以尝试做如下操作

1>N=re:replace(“9.999.999.99”、“\\.”、“,[global,{return,list}])。
"9 999 999 99"
2> Nr=列表:反向(N)。
"99 999 999 9"
3> Nrr=re:replace(“99 999 999 9”、“\\”、“、”、[{return,list}])。
"99,999 999 9"
4> 列表:反向(Nrr)。
"9 999 999,99"
或者您可以尝试创建更多更好的正则表达式

另外,对于将整数/浮点转换为列表,您可以尝试使用
io\u lib:format/2
,例如

1>[Num]=io_lib:format(“~p”,[9999.99])。
["9999.99"]
2> Num。
"9999.99"

您遇到的问题不是erlang中任何标准库都能解决的(AFAIK)。它需要几个操作来产生预期的结果:将浮点转换为字符串、将字符串拆分为数据包、插入2种分隔符以及在开头或结尾插入货币符号。这些任务需要不同的功能。以下代码是您可以执行的操作的示例:

-module (pcur).

-export ([printCurrency/2]).

% X = value to print, must be a float
% Country = an atom representing the country
printCurrency(X,Country) ->
    % convert to string, split and get the different packets
    [Dec|Num] = splitInReversePackets(toReverseString(X)),               
    % get convention for the country 
    {Before,Dot,Sep,After} = currencyConvention(Country),
    % build the result - Beware of unicode!
    Before ++ printValue(Num,Sep,Dot ++ Dec) ++ After.


toReverseString(X) ->  lists:reverse(io_lib:format("~.2f",[X])).  

splitInThousands([A,B,C|R],Acc) -> splitInThousands(R,[[C,B,A]|Acc]);
splitInThousands([A,B|R],Acc)   -> splitInThousands(R,[[B,A]|Acc]);
splitInThousands([A|R],Acc)     -> splitInThousands(R,[[A]|Acc]);
splitInThousands([],Acc)        -> Acc.

splitInReversePackets([A,B,$.|R]) -> lists:reverse(splitInThousands(R,[[B,A]])).

% return a tuple made of {string to print at the beginning,
%                         string to use to separate the integer part and the decimal,
%                         string to use for thousand separator,
%                         string to print at the end}
currencyConvention(us) -> {"",".",",","$"};
currencyConvention(de) -> {"",",","."," Euro"}; % not found how to print the symbol €
currencyConvention(fr) -> {"Euro ",","," ",""};
currencyConvention(_) -> {"",".",",",""}. % default for unknown country symbol

printValue([A|R=[_|_]],Sep,Acc) -> printValue(R,Sep, Sep ++ A ++ Acc); 
printValue([A],_,Acc) -> A ++ Acc.                    
壳内试验:

1> c(pcur).
{ok,pcur}
2> pcur:printCurrency(123456.256,fr).
"Euro 123 456,26"
3> pcur:printCurrency(123456.256,de).
"123.456,26 Euro"
4> pcur:printCurrency(123456.256,us).
"123,456.26$"
5>

编辑阅读其他提案和您的评论,此解决方案显然不是您要去的方向。然而,我认为这是解决你问题的一个有价值的方法。它应该是快速的,对我来说更重要,直截了当,易于阅读和更新(添加货币,分成不同的大小?)

这并不能解决问题。我们可以找到的从浮点到字符串的任何格式都不会添加数千个分隔符,因此您永远没有执行搜索和替换的基础。另外,当
~f
提供更多控制时,我决不会使用
~p
格式化浮动(您的示例对于
99.9
99.9999
之类的数字可能会失败,因为使用
~p
您无法控制精度)。这只是一个可能解决方案的简单示例,但我的意思是,您可以创建自己的一些转换函数,您将在其中执行两个操作,第一个是-将整数转换为列表,第二个是-替换分隔符。如果缺少分隔符,您可以匹配它,例如,在最后两个字符之前添加一个符号点:
N=io_lib:format(“~f”,[9999.9999999])。
然后获取列表的最后两个元素
[Last1,Last2 | T]=lists:reverse(N)。
然后添加该点并反转列表
lists:reverse([Last1]+[Last2]++。因此,从9999.999999开始,我正在寻找一种更优雅的解决方案。我已经编写了自己的函数来处理这种格式,但老实说,对于一个简单而常见的任务来说,这是相当多的代码。如果没有更好的结果,我也会发布我的版本。我希望有人知道一个更好的方法。我明白了,也许可以用正则表达式玩一些有意义的游戏,比如
re:replace(“9999.99”),“\\d(?=(\\d{3})+\\”,“$&,”,[global,{return,list}])。
结果
“$9999.99”
。另外,
{3}
应该是动态的。我们插入千位分隔符的解决方案是朝这个方向的,但是regexp变得更灵活了,因为当然,对于大的数字,必须插入多个分隔符。将
{3}
部分动态化是不够的(也不需要,除非是印度卢比,但幸运的是,我们不需要处理这种奇怪的格式),但分组本身必须容纳多个匹配项,只锚定最后一个匹配项,后跟十进制分隔符。