【问题标题】:How to format a number to string (currency) with thousands and decimal separators using Erlang?如何使用 Erlang 将数字格式化为带有千位和小数分隔符的字符串(货币)?
【发布时间】:2020-01-16 07:45:03
【问题描述】:

我正在使用 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"

如您所见,小数点分隔符的解决方法已经不是很漂亮,处理数千个分隔符也不会更好。我们这里有什么遗漏吗?

【问题讨论】:

    标签: localization formatting erlang currency number-formatting


    【解决方案1】:

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

    -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.                    
    

    在 shell 中测试:

    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>
    

    编辑阅读其他提案和您的 cmets,这个解决方案显然不是您要的方向。尽管如此,在我看来,这是解决您的问题的一种有价值的方法。它对我来说应该更快更重要,直接且易于阅读和更新(添加货币,拆分为不同大小,??)

    【讨论】:

      【解决方案2】:

      我想你可以使用re:replace/4 http://erlang.org/doc/man/re.html#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 = lists:reverse(N).
      "99 999 999 9"
      3> Nrr = re:replace("99 999 999 9", "\\ ", ",",[{return,list}]).
      "99,999 999 9"
      4> lists:reverse(Nrr).
      "9 999 999,99"
      
      

      或者您可以尝试创建更好的正则表达式。

      附:要将整数/浮点数转换为列表,您可以尝试使用io_lib:format/2,例如

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

      【讨论】:

      • 这并不能解决问题。我们可以找到的从浮点数到字符串的任何格式都不会添加千位分隔符,因此您永远没有执行搜索和替换的基础。此外,当~f 提供更多控制权时,我永远不会使用~p 格式化浮点数(对于99.999.9999 等数字,您的示例将失败,因为使用~p,您无法控制精度)。
      • 这只是一个可能的解决方案的简单示例,但我的意思是你可以创建自己的一些转换函数,你会做两个思考,第一个是 - 将整数转换为列表,第二个 - 做替换分隔符。如果没有分隔符,您可以匹配它,例如,在最后两个字符之前添加一个符号点:N = io_lib:format("~f", [9999.999999]).,然后获取列表[Last1, Last2 | T] = lists:reverse(N). 的最后两个元素并添加点并反向返回列表lists:reverse([Last1] ++ [Last2] ++ "." ++ T).。结果"9999.9999.99"来自9999.999999
      • 我正在寻找更优雅的解决方案。我已经编写了自己的函数来处理这种格式,但老实说,对于一个简单而常见的任务来说,这是相当多的代码。如果没有更好的表现,我也会发布我的版本。我希望有人知道更好的方法。
      • 我明白了,使用像re:replace("9999.99", "\\d(?=(\\d{3})+\\.)", "$&,",[global, {return,list}]). 这样的正则表达式作为结果"$9,999.99" 可能是有意义的。附: {3} 应该是动态的。
      • 我们插入千位分隔符的解决方案是朝这个方向发展的,但是正则表达式变得更加灵活,因为当然,对于大数字,必须插入多个分隔符。使{3} 部分动态化是不够的(也不需要,除非是印度卢比,但幸运的是我们不需要处理那种奇怪的格式),但是分组本身必须容纳多个匹配,只锚定最后一个1 后跟小数分隔符。
      猜你喜欢
      • 1970-01-01
      • 2018-09-05
      • 2018-05-11
      • 2014-02-18
      • 2011-06-21
      • 2015-01-19
      • 2015-04-15
      • 1970-01-01
      • 2012-03-08
      相关资源
      最近更新 更多