%%% @doc %%% Formatting and reading functions for Gaju and Puck quantities %%% %%% The numbers involved in dealing with blockchain amounts are enormous %%% by comparison to legacy forms of currency. It isn't so much that %%% thousands of Gajus is hard to reason about, but rather that quadrillions %%% of Pucks is quite hard to even lock on to visually. %%% %%% A normal commas and underscores method of decimal formatting is provided, as %%% `us' formatting along with two additional approaches: %%% - Japanese traditional myriad structure (`jp' style) %%% - An internationalized variant inspired by the Japanese technique over periods %%% (`metric' for SI prefixes, and `legacy' for Anglicized prefixes) %%% %%% These are all accessible via the `amount/N' functions. %%% %%% The `read/1' function can accept any of the output variants as a string and %%% will return the number of pucks indicated by the provided string, allowing for %%% simple copy/paste functionality as well as direct input using any of the %%% supported notations. %%% @end -module(hz_format). -vsn("0.8.2"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0-or-later"). -export([amount/1, amount/2, amount/3, approx_amount/2, approx_amount/3, read/1, one/1, mark/1, price_to_string/1, string_to_price/1]). -spec amount(Pucks) -> Formatted when Pucks :: integer(), Formatted :: string(). %% @doc %% A convenience formatting function. %% ``` %% hz_format:amount(1) -> %% 木0.000,000,000,000,000,001 %% %% hz_format:amount(5000) -> %% 木0.000,000,000,000,005 %% %% hz_format:amount(5000000000000000000) -> %% 木5 %% %% hz_format:amount(500000123000000000000000) -> %% 木500,000.123 %% ''' %% @equiv amount(us, Pucks) amount(Pucks) -> amount(us, Pucks). -spec amount(Style, Pucks) -> Formatted when Style :: us | jp | metric | legacy | {Separator, Span}, Separator :: $, | $_, Span :: 3 | 4, Pucks :: integer(), Formatted :: string(). %% @doc %% A money formatting function. %% ``` %% hz_format:amount(us, 100500040123000000000000000) -> %% 木100,500,040.123 %% %% hz_format:amount(jp, 100500040123000000000000000) -> %% 1億50万40木 12京3000兆本 %% %% hz_format:amount(metric, 100500040123000000000000000) -> %% 木100m 500k 40 G 123p P %% %% hz_format:amount(legacy, 100500040123000000000000000) -> %% 木100m 500k 40 G 123q P %% %% hz_format:amount({$_, 3}, 100500040123000000000000000) -> %% 木100_500_040.123 %% %% hz_format:amount({$_, 4}, 100500040123000000000000000) -> %% 木1_0050_0040.123 %% ''' %% @equiv amount(gaju, Style, Pucks) amount(Style, Pucks) -> amount(gaju, Style, Pucks). -spec amount(Unit, Style, Pucks) -> Formatted when Unit :: gaju | puck, Style :: us | jp | metric | legacy | {Separator, Span}, Separator :: $, | $_, Span :: 3 | 4, Pucks :: integer(), Formatted :: string(). %% @doc %% A simplified format function covering the most common formats desired. %% ``` %% hz_format:amount(gaju, us, 100500040123000004500000000) -> %% 木100,500,040.123,000,004,5 %% %% hz_format:amount(puck, us, 100500040123000004500000000) -> %% 本100,500,040,123,000,004,500,000,000 %% %% hz_format:amount(gaju, jp, 100500040123000004500000000) -> %% 1億50万40木 12京3000兆45億本 %% %% hz_format:amount(puck, jp, 100500040123000004500000000) -> %% 100秭5000垓4012京3000兆45億本 %% %% hz_format:amount(gaju, metric, 100500040123000004500000000) -> %% 木100m 500k 40 G 123p 4g 500m P %% %% hz_format:amount(puck, metric, 100500040123000004500000000) -> %% 本100y 500z 40e 123p 4g 500m P %% %% hz_format:amount(gaju, legacy, 100500040123000004500000000) -> %% 木100m 500k 40 G 123q 4b 500m P %% %% hz_format:amount(puck, legacy, 100500040123000004500000000) -> %% 本100y 500z 40e 123q 4b 500m P %% ''' amount(gaju, us, Pucks) -> western($,, $., 3, all, Pucks); amount(puck, us, Pucks) -> western($,, 3, Pucks); amount(Unit, jp, Pucks) -> jp(Unit, all, Pucks); amount(Unit, metric, Pucks) -> bestern(Unit, ranks(metric), all, Pucks); amount(Unit, legacy, Pucks) -> bestern(Unit, ranks(heresy), all, Pucks); amount(gaju, {Separator, Span}, Pucks) -> western(Separator, $., Span, all, Pucks); amount(puck, {Separator, Span}, Pucks) -> western(Separator, Span, Pucks). -spec approx_amount(Precision, Pucks) -> Serialized when Precision :: all | 0..18, Pucks :: integer(), Serialized :: string(). %% A formatter for decimal notation which permits a precision %% value to be applied to the puck side of the format. %% ``` %% hz_format:approx_amount(3, 100500040123000004500000001) -> %% 木100,500,040.123 %% %% hz_format:approx_amount(13, 100500040123000004500000001) -> %% 木100,500,040.123,000,004,5... %% %% hz_format:approx_amount(all, 100500040123000004500000001) -> %% 木100,500,040.123,000,004,500,000,001 %% ''' %% @equiv approx_amount(us, Precision, Pucks) approx_amount(Precision, Pucks) -> approx_amount(us, Precision, Pucks). -spec approx_amount(Style, Precision, Pucks) -> Serialized when Style :: us | {Separator, Span}, Precision :: all | 0..18, Separator :: $, | $_, Span :: 3 | 4, Pucks :: integer(), Serialized :: string(). %% @doc %% A formatter for decimal notation which permits a precision %% value to be applied to the puck side of the format. %% ``` %% hz_format:approx_amount({$_, 3}, 3, 100500040123000004500000001) -> %% 木100_500_040.123... %% %% hz_format:approx_amount({$_, 4}, 12, 100500040123000004500000001) -> %% 木1_0050_0040.1230_0000_45... %% ''' approx_amount(us, Precision, Pucks) -> western($,, $., 3, Precision, Pucks); approx_amount({Separator, Span}, Precision, Pucks) -> western(Separator, $., Span, Precision, Pucks). western(Separator, Span, Pucks) when Pucks >= 0 -> western2(Separator, Span, Pucks); western(Separator, Span, Pucks) when Pucks < 0 -> [$- | western2(Separator, Span, Pucks * -1)]. western2(Separator, Span, Pucks) -> P = lists:reverse(integer_to_list(Pucks)), [mark(puck) | separate(Separator, Span, P)]. western(Separator, Break, Span, Precision, Pucks) when Pucks >= 0 -> western2(Separator, Break, Span, Precision, Pucks); western(Separator, Break, Span, Precision, Pucks) when Pucks < 0 -> [$- | western2(Separator, Break, Span, Precision, Pucks * -1)]. western2(Separator, _, Span, 0, Pucks) -> G = lists:reverse(integer_to_list(Pucks div one(gaju))), [mark(gaju) | separate(Separator, Span, G)]; western2(Separator, Break, Span, Precision, Pucks) -> SP = integer_to_list(Pucks), Length = length(SP), Over18 = Length > 18, NoPucks = (Pucks rem one(gaju)) =:= 0, case {Over18, NoPucks} of {true, true} -> Gs = lists:reverse(lists:sublist(SP, Length - 18)), [mark(gaju) | separate(Separator, Span, Gs)]; {true, false} -> {PChars, GChars} = lists:split(18, lists:reverse(SP)), H = [mark(gaju) | separate(Separator, Span, GChars)], {P, E} = decimal_pucks(Precision, lists:reverse(PChars)), T = lists:reverse(separate(Separator, Span, P)), lists:flatten([H, Break, T, E]); {false, true} -> [mark(gaju), $0]; {false, false} -> PChars = lists:flatten(string:pad(SP, 18, leading, $0)), {P, E} = decimal_pucks(Precision, PChars), T = lists:reverse(separate(Separator, Span, P)), lists:flatten([mark(gaju), $0, Break, T, E]) end. decimal_pucks(all, PChars) -> RTrailing = lists:reverse(PChars), {lists:reverse(lists:dropwhile(fun(C) -> C =:= $0 end, RTrailing)), ""}; decimal_pucks(Precision, PChars) -> {Significant, Rest} = lists:split(min(Precision, 18), PChars), RTrailing = lists:reverse(Significant), Trailing = lists:reverse(lists:dropwhile(fun(C) -> C =:= $0 end, RTrailing)), case lists:all(fun(C) -> C =:= $0 end, Rest) of true -> {Trailing, ""}; false -> {Trailing, "..."} end. separate(_, _, "") -> ""; separate(S, P, G) -> separate(S, P, 1, G, []). separate(_, _, _, [H], A) -> [H | A]; separate(S, P, P, [H | T], A) -> separate(S, P, 1, T, [S, H | A]); separate(S, P, N, [H | T], A) -> separate(S, P, N + 1, T, [H | A]). bestern(gaju, Ranks, Precision, Pucks) when Pucks >= 0 -> [mark(gaju), bestern2(gaju, Ranks, 3, Precision, Pucks)]; bestern(gaju, Ranks, Precision, Pucks) when Pucks < 0 -> [$-, mark(gaju), bestern2(gaju, Ranks, 3, Precision, Pucks * -1)]; bestern(puck, Ranks, Precision, Pucks) when Pucks >= 0 -> [mark(puck), bestern2(puck, Ranks, 3, Precision, Pucks)]; bestern(puck, Ranks, Precision, Pucks) when Pucks < 0 -> [$-, mark(puck), bestern2(puck, Ranks, 3, Precision, Pucks * -1)]. jp(Unit, Precision, Pucks) when Pucks >= 0 -> bestern2(Unit, ranks(jp), 4, Precision, Pucks); jp(Unit, Precision, Pucks) when Pucks < 0 -> [$-, bestern2(Unit, ranks(jp), 4, Precision, Pucks * -1)]. bestern2(gaju, Ranks, Span, 0, Pucks) -> G = lists:reverse(integer_to_list(Pucks div one(gaju))), case Span of 3 -> period("G", Ranks, G); 4 -> myriad(mark(gaju), Ranks, G) end; bestern2(gaju, Ranks, Span, all, Pucks) -> P = lists:flatten(string:pad(integer_to_list(Pucks rem one(gaju)), 18, leading, $0)), Zilch = lists:all(fun(C) -> C =:= $0 end, P), {H, T} = case {Span, Zilch} of {3, false} -> {bestern2(gaju, Ranks, 3, 0, Pucks), period("P", Ranks, lists:reverse(P))}; {4, false} -> {jp(gaju, 0, Pucks), myriad(mark(puck), Ranks, lists:reverse(P))}; {3, true} -> {bestern2(gaju, Ranks, 3, 0, Pucks), ""}; {4, true} -> {jp(gaju, 0, Pucks), ""} end, lists:flatten([H, " ", T]); bestern2(gaju, Ranks, Span, Precision, Pucks) -> P = lists:flatten(string:pad(integer_to_list(Pucks rem one(gaju)), 18, leading, $0)), H = case Span of 3 -> bestern2(gaju, Ranks, 3, 0, Pucks); 4 -> jp(gaju, 0, Pucks) end, Digits = min(Precision, 18), T = case length(P) < Digits of false -> ReverseP = lists:reverse(lists:sublist(P, Digits)), PuckingString = lists:flatten(string:pad(ReverseP, 18, leading, $0)), case lists:all(fun(C) -> C =:= $0 end, PuckingString) of false -> case Span of 3 -> period("P", Ranks, PuckingString); 4 -> myriad(mark(puck), Ranks, PuckingString) end; true -> "" end; true -> [] end, lists:flatten([H, " ", T]); bestern2(puck, Ranks, Span, all, Pucks) -> P = lists:reverse(integer_to_list(Pucks)), case lists:all(fun(C) -> C =:= $0 end, P) of false -> case Span of 3 -> period("P", Ranks, P); 4 -> myriad(mark(puck), Ranks, P) end; true -> case Span of 3 -> [$0, " P"]; 4 -> [$0, mark(puck)] end end; bestern2(puck, Ranks, Span, Precision, Pucks) -> Digits = min(Precision, 18), P = lists:flatten(string:pad(integer_to_list(Pucks), 18, leading, $0)), case length(P) < Digits of true -> case Span of 3 -> [$0, " P"]; 4 -> [$0, mark(puck)] end; false -> PucksToGive = lists:sublist(P, Digits), PuckingString = lists:flatten(string:pad(lists:reverse(PucksToGive), 18, leading, $0)), case lists:all(fun(C) -> C =:= $0 end, PuckingString) of false -> case Span of 3 -> period("P", Ranks, PuckingString); 4 -> myriad(mark(puck), Ranks, PuckingString) end; true -> case Span of 3 -> [$0, " P"]; 4 -> [$0, mark(puck)] end end end. period(Symbol, Ranks, [$0, $0, $0 | PT]) -> rank3(Ranks, PT, [Symbol]); period(Symbol, Ranks, [P3, $0, $0 | PT]) -> rank3(Ranks, PT, [P3, 32, Symbol]); period(Symbol, Ranks, [P3, P2, $0 | PT]) -> rank3(Ranks, PT, [P2, P3, 32, Symbol]); period(Symbol, Ranks, [P3, P2, P1 | PT]) -> rank3(Ranks, PT, [P1, P2, P3, 32, Symbol]); period(Symbol, _, [P3]) -> [P3, 32, Symbol]; period(Symbol, _, [P3, P2]) -> [P2, P3, 32, Symbol]. rank3([_ | RT], [$0, $0, $0 | PT], A) -> rank3(RT, PT, A); rank3([RH | RT], [P3, $0, $0 | PT], A) -> rank3(RT, PT, [P3, RH | A]); rank3([RH | RT], [P3, P2, $0 | PT], A) -> rank3(RT, PT, [P2, P3, RH | A]); rank3([RH | RT], [P3, P2, P1 | PT], A) -> rank3(RT, PT, [P1, P2, P3, RH | A]); rank3(_, [$0, $0, $0], A) -> A; rank3(_, [$0, $0], A) -> A; rank3(_, [$0], A) -> A; rank3(_, [], A) -> A; rank3([RH | _], [P3, $0, $0], A) -> [P3, RH | A]; rank3([RH | _], [P3, $0], A) -> [P3, RH | A]; rank3([RH | _], [P3], A) -> [P3, RH | A]; rank3([RH | _], [P3, P2, $0], A) -> [P2, P3, RH | A]; rank3([RH | _], [P3, P2], A) -> [P2, P3, RH | A]; rank3([RH | _], [P3, P2, P1], A) -> [P1, P2, P3, RH | A]. myriad(Symbol, Ranks, [$0, $0, $0, $0 | PT]) -> rank4(Ranks, PT, [Symbol]); myriad(Symbol, Ranks, [P4, $0, $0, $0 | PT]) -> rank4(Ranks, PT, [P4, Symbol]); myriad(Symbol, Ranks, [P4, P3, $0, $0 | PT]) -> rank4(Ranks, PT, [P3, P4, Symbol]); myriad(Symbol, Ranks, [P4, P3, P2, $0 | PT]) -> rank4(Ranks, PT, [P2, P3, P4, Symbol]); myriad(Symbol, Ranks, [P4, P3, P2, P1 | PT]) -> rank4(Ranks, PT, [P1, P2, P3, P4, Symbol]); myriad(Symbol, _, [P4]) -> [P4, Symbol]; myriad(Symbol, _, [P4, P3]) -> [P3, P4, Symbol]; myriad(Symbol, _, [P4, P3, P2]) -> [P2, P3, P4, Symbol]. rank4([_ | RT], [$0, $0, $0, $0 | PT], A) -> rank4(RT, PT, A); rank4([RH | RT], [P4, $0, $0, $0 | PT], A) -> rank4(RT, PT, [P4, RH | A]); rank4([RH | RT], [P4, P3, $0, $0 | PT], A) -> rank4(RT, PT, [P3, P4, RH | A]); rank4([RH | RT], [P4, P3, P2, $0 | PT], A) -> rank4(RT, PT, [P2, P3, P4, RH | A]); rank4([RH | RT], [P4, P3, P2, P1 | PT], A) -> rank4(RT, PT, [P1, P2, P3, P4, RH | A]); rank4(_, [$0, $0, $0, $0], A) -> A; rank4(_, [$0, $0, $0], A) -> A; rank4(_, [$0, $0], A) -> A; rank4(_, [$0], A) -> A; rank4(_, [], A) -> A; rank4([RH | _], [P4, $0, $0, $0], A) -> [P4, RH | A]; rank4([RH | _], [P4, $0, $0], A) -> [P4, RH | A]; rank4([RH | _], [P4, $0], A) -> [P4, RH | A]; rank4([RH | _], [P4], A) -> [P4, RH | A]; rank4([RH | _], [P4, P3, $0, $0], A) -> [P3, P4, RH | A]; rank4([RH | _], [P4, P3, $0], A) -> [P3, P4, RH | A]; rank4([RH | _], [P4, P3], A) -> [P3, P4, RH | A]; rank4([RH | _], [P4, P3, P2, $0], A) -> [P2, P3, P4, RH | A]; rank4([RH | _], [P4, P3, P2], A) -> [P2, P3, P4, RH | A]; rank4([RH | _], [P4, P3, P2, P1], A) -> [P1, P2, P3, P4, RH | A]. ranks(jp) -> "万億兆京垓秭穣溝澗正載極"; ranks(metric) -> ["k ", "m ", "g ", "t ", "p ", "e ", "z ", "y ", "r ", "Q "]; ranks(heresy) -> ["k ", "m ", "b ", "t ", "q ", "e ", "z ", "y ", "r ", "Q "]. mark(gaju) -> $木; mark(puck) -> $本. one(gaju) -> 1_000_000_000_000_000_000; one(puck) -> 1. -spec read(Format) -> Result when Format :: string(), Result :: {ok, Pucks} | error, Pucks :: integer(). %% @doc %% Convert any valid string formatted representation and output a value in pucks. %% NOTE: This function does not accept approximated values. %% ``` %% 1> hz_format:read("木100,500,040.123,000,004,5"). %% {ok,100500040123000004500000000} %% 2> hz_format:read("本100,500,040,123,000,004,500,000,000"). %% {ok,100500040123000004500000000} %% 3> hz_format:read("1億50万40木 12京3000兆45億本"). %% {ok,100500040123000004500000000} %% 4> hz_format:read("100秭5000垓4012京3000兆45億本"). %% {ok,100500040123000004500000000} %% 5> hz_format:read("木100m 500k 40 G 123p 4g 500m P"). %% {ok,100500040123000004500000000} %% 6> hz_format:read("本100y 500z 40e 123p 4g 500m P"). %% {ok,100500040123000004500000000} %% 7> hz_format:read("木100m 500k 40 G 123q 4b 500m P"). %% {ok,100500040123000004500000000} %% 8> hz_format:read("本100y 500z 40e 123q 4b 500m P"). %% {ok,100500040123000004500000000} %% ''' read([$木 | Rest]) -> read_w_gajus(Rest, []); read([$本 | Rest]) -> read_w_pucks(Rest, []); read([C | Rest]) when C =:= $- orelse C =:= $− orelse C =:= $- -> case read(Rest) of {ok, Pucks} -> {ok, Pucks * -1}; Error -> Error end; read([C | Rest]) when C =:= 32 orelse % ASCII space C =:= 12288 orelse % full-width space C =:= $\t orelse C =:= $\r orelse C =:= $\n -> read(Rest); read([C | Rest]) when $0 =< C andalso C =< $9 -> read(Rest, [C], []); read([C | Rest]) when $0 =< C andalso C =< $9 -> NumC = C - $0 + $0, read(Rest, [NumC], []); read(String) when is_binary(String) -> read(binary_to_list(String)); read(_) -> error. read_w_gajus([C | Rest], A) when $0 =< C andalso C =< $9 -> read_w_gajus(Rest, [C | A]); read_w_gajus([C | Rest], A) when $0 =< C andalso C =< $9 -> NumC = C - $0 + $0, read_w_gajus(Rest, [NumC | A]); read_w_gajus([$, | Rest], A) -> read_w_gajus(Rest, A); read_w_gajus([$_ | Rest], A) -> read_w_gajus(Rest, A); read_w_gajus([$. | Rest], A) -> case read_w_pucks(Rest, []) of {ok, P} -> G = list_to_integer(lists:reverse(A)) * one(gaju), {ok, G + P}; Error -> Error end; read_w_gajus([], A) -> G = list_to_integer(lists:reverse(A)) * one(gaju), {ok, G}; read_w_gajus([C, 32 | Rest], A) -> read(Rest, [], [{C, A}]); read_w_gajus([32, $G, 32 | Rest], A) -> read(Rest, [], [{$G, A}], []); read_w_gajus([32, $G], A) -> calc([{$G, A}], []); read_w_gajus([32, $P], A) -> calc([], [{$P, A}]); read_w_gajus(_, _) -> error. read_w_pucks([C | Rest], A) when $0 =< C andalso C =< $9 -> read_w_pucks(Rest, [C | A]); read_w_pucks([C | Rest], A) when $0 =< C andalso C =< $9 -> NumC = C - $0 + $0, read_w_pucks(Rest, [NumC | A]); read_w_pucks([$, | Rest], A) -> read_w_pucks(Rest, A); read_w_pucks([$_ | Rest], A) -> read_w_pucks(Rest, A); read_w_pucks([C, 32 | Rest], A) -> read(Rest, [], [], [{C, A}]); read_w_pucks([32, $P], A) -> calc([], [{$P, A}]); read_w_pucks([], A) -> Padded = lists:flatten(string:pad(lists:reverse(A), 18, trailing, $0)), {ok, list_to_integer(Padded)}. read([C | Rest], A, G) when $0 =< C andalso C =< $9 -> read(Rest, [C | A], G); read([C | Rest], A, G) when $0 =< C andalso C =< $9 -> NumC = C - $0 + $0, read(Rest, [NumC | A], G); read([$木], A, G) -> calc([{$G, A} | G], []); read([$G], A, G) -> calc([{$G, A} | G], []); read([32, $G], A, G) -> calc([{$G, A} | G], []); read([32, $G, 32 | Rest], A, G) -> read(Rest, [], [{$G, A} | G], []); read([$木, 32 | Rest], A, G) -> read(Rest, [], [{$G, A} | G], []); read([$G, 32 | Rest], A, G) -> read(Rest, [], [{$G, A} | G], []); read([$本], A, P) -> calc([], [{$P, A} | P]); read([$P], A, P) -> calc([], [{$P, A} | P]); read([32, $P], A, P) -> calc([], [{$P, A} | P]); read([C, 32 | Rest], A, G) -> read(Rest, [], [{C, A} | G]); read([$, | Rest], A, []) -> read_w_gajus(Rest, A); read([$_ | Rest], A, []) -> read_w_gajus(Rest, A); read([$. | Rest], A, []) -> case read_w_pucks(Rest, []) of {ok, P} -> G = list_to_integer(lists:reverse(A)) * one(gaju), {ok, G + P}; Error -> Error end; read([C | Rest], A, G) -> read(Rest, [], [{C, A} | G]); read([], A, []) -> read_w_gajus([], A); read(_, _, _) -> error. read([C | Rest], A, G, P) when $0 =< C andalso C =< $9 -> read(Rest, [C | A], G, P); read([C | Rest], A, G, P) when $0 =< C andalso C =< $9 -> NumC = C - $0 + $0, read(Rest, [NumC | A], G, P); read([$本], A, G, P) -> calc(G, [{$P, A} | P]); read([$P], A, G, P) -> calc(G, [{$P, A} | P]); read([32, $P], A, G, P) -> calc(G, [{$P, A} | P]); read([C, 32 | Rest], A, G, P) -> read(Rest, [], G, [{C, A} | P]); read([C | Rest], A, G, P) -> read(Rest, [], G, [{C, A} | P]); read(_, _, _, _) -> error. calc(G, P) -> case calc(gaju, G, 0) of {ok, Gajus} -> case calc(puck, P, 0) of {ok, Pucks} -> {ok, Gajus + Pucks}; error -> error end; error -> error end. calc(U, [{_, []} | S], A) -> calc(U, S, A); calc(U, [{M, Cs} | S], A) -> case magnitude(M) of {ok, J} -> N = list_to_integer(lists:reverse(Cs)) * J * one(U), calc(U, S, A + N); Error -> Error end; calc(_, [], A) -> {ok, A}. magnitude($G) -> {ok, 1}; magnitude($P) -> {ok, 1}; magnitude(Mark) -> case rank(Mark, ranks(jp), 1_0000, 1) of {ok, J} -> {ok, J}; error -> case rank([Mark, 32], ranks(metric), 1_000, 1) of {ok, J} -> {ok, J}; error -> rank([Mark, 32], ranks(heresy), 1_000, 1) end end. rank(Mark, [Mark | _], Magnitude, Sum) -> {ok, Sum * Magnitude}; rank(Mark, [_ | Rest], Magnitude, Sum) -> rank(Mark, Rest, Magnitude, Sum * Magnitude); rank(_, [], _, _) -> error. -spec price_to_string(Pucks) -> Gajus when Pucks :: integer(), Gajus :: string(). %% @doc %% A simplified formatting function that converts an integer value in Pucks to a string representation %% in Gajus. Useful for formatting generic output for UI elements price_to_string(Pucks) -> Gaju = one(gaju), H = integer_to_list(Pucks div Gaju), R = Pucks rem Gaju, case string:strip(lists:flatten(io_lib:format("~18..0w", [R])), right, $0) of [] -> H; T -> string:join([H, T], ".") end. -spec string_to_price(Gajus) -> Pucks when Gajus :: string(), Pucks :: integer(). %% @doc %% A simplified formatting function that converts a Gaju value represented as a string to an %% integer value in Pucks. string_to_price(String) -> case string:split(String, ".") of [H] -> join_price(H, "0"); [H, T] -> join_price(H, T); _ -> {error, bad_price} end. join_price(H, T) -> try Parts = [H, string:pad(T, 18, trailing, $0)], Price = list_to_integer(unicode:characters_to_list(Parts)), case Price < 0 of false -> {ok, Price}; true -> {error, negative_price} end catch error:R -> {error, R} end.