This commit is contained in:
Craig Everett 2025-12-22 11:14:13 +09:00
parent b542205c0e
commit 2f93c4d503

View File

@ -26,8 +26,10 @@
-copyright("Craig Everett <ceverett@tsuriai.jp>").
-license("GPL-3.0-or-later").
-export([amount/1, amount/2, amount/3, amount/4,
-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
@ -35,6 +37,19 @@
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) ->
@ -49,6 +64,25 @@ amount(Pucks) ->
Formatted :: string().
%% @doc
%% A money formatting function.
%% ```
%% hz_format:amount(us, 100500040123000000000000000) ->
%% 100,500,040.123
%%
%% hz_format:amount(jp, 100500040123000000000000000) ->
%% 15040 123000
%%
%% 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) ->
@ -64,6 +98,31 @@ amount(Style, Pucks) ->
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) ->
%% 15040 12300045
%%
%% hz_format:amount(puck, jp, 100500040123000004500000000) ->
%% 10050004012300045
%%
%% 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);
@ -81,66 +140,51 @@ amount(puck, {Separator, Span}, Pucks) ->
western(Separator, Span, Pucks).
-spec amount(Unit, Style, Precision, Pucks) -> Serialized
when Unit :: gaju | puck,
Style :: us | jp | metric | legacy | {Separator, Span},
-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.
%% @equiv approx_amount(us, Precision, Pucks).
%% ```
%% 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
%% '''
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 flexible, if annoyingly complex, formatting function.
%%
%% A formatter for decimal notation which permits a precision
%% value to be applied to the puck side of the format.
%% ```
%% amount(gaju, us, 3, 123456789123456789123456789) ->
%% "木123,456,789.123...".
%% hz_format:approx_amount({$_, 3}, 3, 100500040123000004500000001) ->
%% 100_500_040.123...
%%
%% amount(gaju, us, 3, 123456789123000000000000000) ->
%% "木123,456,789.123".
%%
%% amount(gaju, {$,, 3}, 3, 123456789123456789123456789) ->
%% "木123,456,789.123...".
%%
%% amount(gaju, {$,, 3}, 6, 123456789123000000000000000) ->
%% "木123,456,789.123"
%%
%% amount(gaju, {$_, 4}, 10, 123456789123456789123456789) ->
%% "木1_2345_6789.1234_5678_91..."
%%
%% amount(gaju, jp, 3, 123456789123456789123456789) ->
%% "1億2345万6789木 12京3000兆本"
%%
%% amount(gaju, jp, 6, 123456789123456789123456789) ->
%% "1億2345万6789木 12京3456兆本"
%%
%% amount(gaju, jp, 0, 123456789123456789123456789) ->
%% "1億2345万6789木"
%%
%% amount(puck, jp, all, 123456789123456789123456789) ->
%% "123秭4567垓8912京3456兆7891億2345万6789本"
%% hz_format:approx_amount({$_, 4}, 12, 100500040123000004500000001) ->
%% 1_0050_0040.1230_0000_45...
%% '''
amount(gaju, us, Precision, Pucks) ->
approx_amount(us, Precision, Pucks) ->
western($,, $., 3, Precision, Pucks);
amount(gaju, jp, Precision, Pucks) ->
jp(gaju, Precision, Pucks);
amount(gaju, metric, Precision, Pucks) ->
bestern(gaju, ranks(metric), Precision, Pucks);
amount(gaju, legacy, Precision, Pucks) ->
bestern(gaju, ranks(heresy), Precision, Pucks);
amount(gaju, {Separator, Span}, Precision, Pucks) ->
western(Separator, $., Span, Precision, Pucks);
amount(puck, us, _, Pucks) ->
western($,, 3, Pucks);
amount(puck, jp, _, Pucks) ->
jp(puck, all, Pucks);
amount(puck, metric, _, Pucks) ->
bestern(puck, ranks(metric), all, Pucks);
amount(puck, legacy, _, Pucks) ->
bestern(puck, ranks(heresy), all, Pucks);
amount(puck, {Separator, Span}, _, Pucks) ->
western(Separator, Span, Pucks).
approx_amount({Separator, Span}, Precision, Pucks) ->
western(Separator, $., Span, Precision, Pucks).
western(Separator, Span, Pucks) when Pucks >= 0 ->
@ -150,7 +194,7 @@ western(Separator, Span, Pucks) when Pucks < 0 ->
western2(Separator, Span, Pucks) ->
P = lists:reverse(integer_to_list(Pucks)),
[puck_mark() | separate(Separator, Span, P)].
[mark(puck) | separate(Separator, Span, P)].
western(Separator, Break, Span, Precision, Pucks) when Pucks >= 0 ->
@ -160,30 +204,30 @@ western(Separator, Break, Span, Precision, Pucks) when Pucks < 0 ->
western2(Separator, _, Span, 0, Pucks) ->
G = lists:reverse(integer_to_list(Pucks div one_gaju())),
[gaju_mark() | separate(Separator, Span, G)];
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,
NoPucks = (Pucks rem one(gaju)) =:= 0,
case {Over18, NoPucks} of
{true, true} ->
Gs = lists:reverse(lists:sublist(SP, Length - 18)),
[gaju_mark() | separate(Separator, Span, Gs)];
[mark(gaju) | separate(Separator, Span, Gs)];
{true, false} ->
{PChars, GChars} = lists:split(18, lists:reverse(SP)),
H = [gaju_mark() | separate(Separator, Span, GChars)],
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} ->
[gaju_mark(), $0];
[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([gaju_mark(), $0, Break, T, E])
lists:flatten([mark(gaju), $0, Break, T, E])
end.
decimal_pucks(all, PChars) ->
@ -212,13 +256,13 @@ separate(S, P, N, [H | T], A) ->
bestern(gaju, Ranks, Precision, Pucks) when Pucks >= 0 ->
[gaju_mark(), bestern2(gaju, Ranks, 3, Precision, Pucks)];
[mark(gaju), bestern2(gaju, Ranks, 3, Precision, Pucks)];
bestern(gaju, Ranks, Precision, Pucks) when Pucks < 0 ->
[$-, gaju_mark(), bestern2(gaju, Ranks, 3, Precision, Pucks * -1)];
[$-, mark(gaju), bestern2(gaju, Ranks, 3, Precision, Pucks * -1)];
bestern(puck, Ranks, Precision, Pucks) when Pucks >= 0 ->
[puck_mark(), bestern2(puck, Ranks, 3, Precision, Pucks)];
[mark(puck), bestern2(puck, Ranks, 3, Precision, Pucks)];
bestern(puck, Ranks, Precision, Pucks) when Pucks < 0 ->
[$-, puck_mark(), bestern2(puck, Ranks, 3, Precision, Pucks * -1)].
[$-, mark(puck), bestern2(puck, Ranks, 3, Precision, Pucks * -1)].
jp(Unit, Precision, Pucks) when Pucks >= 0 ->
bestern2(Unit, ranks(jp), 4, Precision, Pucks);
@ -226,24 +270,24 @@ 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())),
G = lists:reverse(integer_to_list(Pucks div one(gaju))),
case Span of
3 -> period("G", Ranks, G);
4 -> myriad(gaju_mark(), 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)),
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(puck_mark(), 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)),
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);
@ -259,7 +303,7 @@ bestern2(gaju, Ranks, Span, Precision, Pucks) ->
false ->
case Span of
3 -> period("P", Ranks, PuckingString);
4 -> myriad(puck_mark(), Ranks, PuckingString)
4 -> myriad(mark(puck), Ranks, PuckingString)
end;
true ->
""
@ -274,12 +318,12 @@ bestern2(puck, Ranks, Span, all, Pucks) ->
false ->
case Span of
3 -> period("P", Ranks, P);
4 -> myriad(puck_mark(), Ranks, P)
4 -> myriad(mark(puck), Ranks, P)
end;
true ->
case Span of
3 -> [$0, " P"];
4 -> [$0, puck_mark()]
4 -> [$0, mark(puck)]
end
end;
bestern2(puck, Ranks, Span, Precision, Pucks) ->
@ -289,7 +333,7 @@ bestern2(puck, Ranks, Span, Precision, Pucks) ->
true ->
case Span of
3 -> [$0, " P"];
4 -> [$0, puck_mark()]
4 -> [$0, mark(puck)]
end;
false ->
PucksToGive = lists:sublist(P, Digits),
@ -298,12 +342,12 @@ bestern2(puck, Ranks, Span, Precision, Pucks) ->
false ->
case Span of
3 -> period("P", Ranks, PuckingString);
4 -> myriad(puck_mark(), Ranks, PuckingString)
4 -> myriad(mark(puck), Ranks, PuckingString)
end;
true ->
case Span of
3 -> [$0, " P"];
4 -> [$0, puck_mark()]
4 -> [$0, mark(puck)]
end
end
end.
@ -418,11 +462,11 @@ ranks(heresy) ->
["k ", "m ", "b ", "t ", "q ", "e ", "z ", "y ", "r ", "Q "].
gaju_mark() -> $木.
mark(gaju) -> $木;
mark(puck) -> $本.
puck_mark() -> $本.
one_gaju() -> 1_000_000_000_000_000_000.
one(gaju) -> 1_000_000_000_000_000_000;
one(puck) -> 1.
-spec read(Format) -> Result
@ -430,10 +474,26 @@ one_gaju() -> 1_000_000_000_000_000_000.
Result :: {ok, Pucks} | error,
Pucks :: integer().
%% @doc
%% Convery any valid string formatted representation and output a value in pucks.
%% This routine can fail in the special case of `ch' style formatting with a single
%% comma and/or a single period in it, as this can trigger misinterpretation as `us'
%% style. When in doubt, always call `read/2' with a style specified.
%% 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, []);
@ -459,6 +519,8 @@ read([C | Rest]) when $0 =< C andalso C =< $9 ->
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.
@ -474,13 +536,13 @@ 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(),
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(),
G = list_to_integer(lists:reverse(A)) * one(gaju),
{ok, G};
read_w_gajus([C, 32 | Rest], A) ->
read(Rest, [], [{C, A}]);
@ -488,7 +550,10 @@ read_w_gajus([32, $G, 32 | Rest], A) ->
read(Rest, [], [{$G, A}], []);
read_w_gajus([32, $G], A) ->
calc([{$G, A}], []);
read_w_gajus(_, _) ->
read_w_gajus([32, $P], A) ->
calc([], [{$P, A}]);
read_w_gajus(A, B) ->
io:format("A: ~ts, B: ~p~n", [A, B]),
error.
read_w_pucks([C | Rest], A) when $0 =< C andalso C =< $9 ->
@ -500,6 +565,10 @@ 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)}.
@ -514,6 +583,10 @@ 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) ->
@ -522,6 +595,8 @@ 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([C | Rest], A, G) ->
@ -540,6 +615,8 @@ 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) ->
@ -564,7 +641,7 @@ calc(U, [{_, []} | S], A) ->
calc(U, [{M, Cs} | S], A) ->
case magnitude(M) of
{ok, J} ->
N = list_to_integer(lists:reverse(Cs)) * J * unit(U),
N = list_to_integer(lists:reverse(Cs)) * J * one(U),
calc(U, S, A + N);
Error ->
Error
@ -572,9 +649,6 @@ calc(U, [{M, Cs} | S], A) ->
calc(_, [], A) ->
{ok, A}.
unit(gaju) -> one_gaju();
unit(puck) -> 1.
magnitude($G) ->
{ok, 1};
@ -607,7 +681,7 @@ rank(_, [], _, _) ->
%% in Gajus. Useful for formatting generic output for UI elements
price_to_string(Pucks) ->
Gaju = 1_000_000_000_000_000_000,
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