hakuzaru/src/hz_format.erl

729 lines
23 KiB
Erlang
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

%%% @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 <ceverett@tsuriai.jp>").
-copyright("Craig Everett <ceverett@tsuriai.jp>").
-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 $ =< C andalso C =< $ ->
NumC = C - $ + $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 $ =< C andalso C =< $ ->
NumC = C - $ + $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 $ =< C andalso C =< $ ->
NumC = C - $ + $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 $ =< C andalso C =< $ ->
NumC = C - $ + $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 $ =< C andalso C =< $ ->
NumC = C - $ + $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.