Revision 5c0b8569
Von Sven Schöling vor etwa 4 Jahren hinzugefügt
SL/Form.pm | ||
---|---|---|
700 | 700 |
} |
701 | 701 |
# |
702 | 702 |
|
703 |
sub format_amount { shift; goto &SL::Helper::Number::_number; } |
|
703 |
sub format_amount { |
|
704 |
my ($self, $myconfig, $amount, $places, $dash) = @_; |
|
705 |
SL::Helper::Number::_format_number($amount, $places, %$myconfig, dash => $dash); |
|
706 |
} |
|
704 | 707 |
|
705 | 708 |
sub format_amount_units { |
706 | 709 |
$main::lxdebug->enter_sub(); |
... | ... | |
787 | 790 |
|
788 | 791 |
# |
789 | 792 |
|
790 |
sub parse_amount { shift; goto &SL::Helper::Number::_parse_number; } |
|
793 |
sub parse_amount { |
|
794 |
my ($self, $myconfig, $amount) = @_; |
|
795 |
SL::Helper::Number::_parse_number($amount, %$myconfig); |
|
796 |
} |
|
791 | 797 |
|
792 | 798 |
sub round_amount { shift; goto &SL::Helper::Number::_round_number; } |
793 | 799 |
|
SL/Helper/Number.pm | ||
---|---|---|
6 | 6 |
use Config; |
7 | 7 |
|
8 | 8 |
our @EXPORT_OK = qw( |
9 |
_total _round_total
|
|
10 |
_number _round_number
|
|
9 |
_format_number _round_number
|
|
10 |
_format_total _round_total
|
|
11 | 11 |
_parse_number |
12 | 12 |
); |
13 |
our %EXPORT_TAGS = (all => \@EXPORT_OK); |
|
14 |
|
|
15 |
sub _number { |
|
16 |
my ($myconfig, $amount, $places, $dash) = @_; |
|
17 |
$amount ||= 0; |
|
18 |
$dash ||= ''; |
|
19 |
my $neg = $amount < 0; |
|
13 |
our %EXPORT_TAGS = (ALL => \@EXPORT_OK); |
|
14 |
|
|
15 |
sub _format_number { |
|
16 |
my ($amount, $places, %params) = @_; |
|
17 |
$amount ||= 0; |
|
18 |
my $dash = $params{dash} // ''; |
|
19 |
my $numberformat = $params{numberformat} // $::myconfig{numberformat}; |
|
20 |
my $neg = $amount < 0; |
|
20 | 21 |
my $force_places = defined $places && $places >= 0; |
21 | 22 |
|
22 | 23 |
$amount = _round_number($amount, abs $places) if $force_places; |
... | ... | |
29 | 30 |
|
30 | 31 |
$amount =~ s/0*$// unless defined $places && $places == 0; # cull trailing 0s |
31 | 32 |
|
32 |
my @d = map { s/\d//g; reverse split // } my $tmp = $myconfig->{numberformat}; # get delim chars
|
|
33 |
my @d = reverse $numberformat =~ /(\D)/g; # get delim chars
|
|
33 | 34 |
my @p = split(/\./, $amount); # split amount at decimal point |
34 | 35 |
|
35 | 36 |
$p[0] =~ s/\B(?=(...)*$)/$d[1]/g if $d[1]; # add 1,000 delimiters |
... | ... | |
57 | 58 |
$places //= 0; |
58 | 59 |
|
59 | 60 |
if ($adjust) { |
61 |
no warnings 'once'; |
|
60 | 62 |
my $precision = $::instance_conf->get_precision || 0.01; |
61 | 63 |
return _round_number( _round_number($amount / $precision, 0) * $precision, $places); |
62 | 64 |
} |
... | ... | |
95 | 97 |
} |
96 | 98 |
|
97 | 99 |
sub _parse_number { |
98 |
my ($myconfig, $amount) = @_;
|
|
100 |
my ($amount, %params) = @_;
|
|
99 | 101 |
|
100 | 102 |
return 0 if !defined $amount || $amount eq ''; |
101 | 103 |
|
102 |
if ( ($myconfig->{numberformat} eq '1.000,00') |
|
103 |
|| ($myconfig->{numberformat} eq '1000,00')) { |
|
104 |
my $numberformat = $params{numberformat} // $::myconfig{numberformat}; |
|
105 |
|
|
106 |
if ( ($numberformat eq '1.000,00') |
|
107 |
|| ($numberformat eq '1000,00')) { |
|
104 | 108 |
$amount =~ s/\.//g; |
105 | 109 |
$amount =~ s/,/\./g; |
106 | 110 |
} |
107 | 111 |
|
108 |
if ($myconfig->{numberformat} eq "1'000.00") {
|
|
112 |
if ($numberformat eq "1'000.00") {
|
|
109 | 113 |
$amount =~ s/\'//g; |
110 | 114 |
} |
111 | 115 |
|
... | ... | |
120 | 124 |
return scalar(eval($amount)) * 1 ; |
121 | 125 |
} |
122 | 126 |
|
123 |
sub _total { _number(\%::myconfig, $_[0], 2) } |
|
124 |
|
|
125 |
sub _round_total { _round_number($_[0], 2) } |
|
127 |
sub _format_total { _format_number($_[0], 2, @_[1..$#_]) } |
|
128 |
sub _round_total { _round_number($_[0], 2, @_[1..$#_]) } |
|
126 | 129 |
|
127 | 130 |
1; |
128 | 131 |
|
... | ... | |
138 | 141 |
|
139 | 142 |
use SL::Helper::Number qw(all); |
140 | 143 |
|
141 |
my $str = _number(\%::myconfig, $val, 2); |
|
142 |
my $total = _total($val); # rounded to 2 |
|
144 |
my $str = _format_number($val, 2); # round to 2 |
|
145 |
my $str = _format_number($val, 2, %::myconfig); # also works, is implied |
|
146 |
my $str = _format_number($val, 2, numberformat => '1.000,00'); # with custom numberformat |
|
147 |
my $total = _format_total($val); # round to 2 |
|
148 |
my $total = _format_total($val, numberformat => '1.000,00'); |
|
143 | 149 |
|
144 |
my $val = _parse_number(\%::myconfig, $str); |
|
150 |
my $val = _parse_number($str); # parse with the current numberformat |
|
151 |
my $val = _parse_number($str, numberformat => '1.000,00'); # parse with the current numberformat |
|
145 | 152 |
|
146 |
my $str = _round_number(\%::myconfig, $val, 2);
|
|
153 |
my $str = _round_number($val, 2); |
|
147 | 154 |
my $total = _round_total($val); # rounded to 2 |
148 | 155 |
|
149 | 156 |
=head1 DESCRIPTION |
150 | 157 |
|
151 |
This package contains all the number parsing/formating functions that were previously in SL::Form. |
|
158 |
This package contains all the number parsing/formating functions that were |
|
159 |
previously in SL::Form. |
|
152 | 160 |
|
153 | 161 |
Instead of invoking them as methods on C<$::form> these are pure functions. |
154 | 162 |
|
... | ... | |
156 | 164 |
|
157 | 165 |
=over 4 |
158 | 166 |
|
159 |
=item * C<_number MYCONFIG VALUE PLACES DASH>
|
|
167 |
=item * C<_format_number VALUE PLACES PARAMS>
|
|
160 | 168 |
|
161 |
The old C<SL::Form::format_amount>. C<MYCONFIG> is expected to be a hashref |
|
162 |
with a C<numberformat> entry. Usually C<\%::myconfig> will be passed. |
|
169 |
The old C<SL::Form::format_amount> with a different signature. |
|
163 | 170 |
|
164 | 171 |
The value is expected to be a numeric value, but undef and empty string will be |
165 | 172 |
vivified to 0 for convinience. Bigints are supported. |
166 | 173 |
|
167 | 174 |
For the semantics of places, see L</PLACES>. |
168 | 175 |
|
169 |
The dash parameter allows to change the formatting of positive and negative
|
|
170 |
numbers to alternative ones. If C<-> is given for dash, negative numbers will
|
|
176 |
If C<params> contains a dash parameter, it will change the formatting of
|
|
177 |
positive/negative numbers. If C<-> is given for dash, negative numbers will
|
|
171 | 178 |
instead be formatted with prentheses. If C<DRCR> is given, the numbers will be |
172 | 179 |
formatted absolute, but suffixed with the localized versions of C<DR> and |
173 | 180 |
C<CR>. |
174 | 181 |
|
175 |
=item * _total |
|
182 |
=item * _format_total
|
|
176 | 183 |
|
177 | 184 |
A curried version used for formatting ledger entries. C<myconfig> is set from the |
178 | 185 |
current user, C<places> is set to 2. C<dash> is left empty. |
179 | 186 |
|
180 |
=item * _parse_number MYCONFIG VALUE
|
|
187 |
=item * _parse_number VALUE PARAMS
|
|
181 | 188 |
|
182 |
Parses expressions into numbers. C<MYCONFIG> is expected to be a hashref
|
|
183 |
with a C<numberformat> entry.
|
|
189 |
Parses expressions into numbers. C<PARAMS> may contain C<numberformat> just
|
|
190 |
like with C<L/_format_amount>.
|
|
184 | 191 |
|
185 |
Also implements basic arithmetic interprtation, so that C<2 * 1400> is |
|
192 |
Also implements basic arithmetic interpretation, so that C<2 * 1400> is
|
|
186 | 193 |
interpreted as 2800. |
187 | 194 |
|
188 | 195 |
=item * _round_number VALUE PLACES |
... | ... | |
210 | 217 |
|
211 | 218 |
In that case a representation is chosen that looks sufficiently human. For |
212 | 219 |
example C<1/10> equals C<.1000000000000000555> but will be displayed as the |
213 |
localzed version of 0.1. |
|
220 |
localized version of 0.1.
|
|
214 | 221 |
|
215 | 222 |
=item * 0 |
216 | 223 |
|
t/helper/number.t | ||
---|---|---|
1 |
use Test::More tests => 173; |
|
2 |
|
|
3 |
use lib 't'; |
|
4 |
|
|
5 |
use SL::Helper::Number qw(:ALL); |
|
6 |
|
|
7 |
use_ok 'Support::TestSetup'; |
|
8 |
|
|
9 |
Support::TestSetup::login(); |
|
10 |
|
|
11 |
# format |
|
12 |
|
|
13 |
sub test_format { |
|
14 |
my ($expected, $amount, $places, $numberformat, $dash, $comment) = @_; |
|
15 |
|
|
16 |
my $other_numberformat = $numberformat eq '1.000,00' ? '1,000.00' : '1.000,00'; |
|
17 |
|
|
18 |
is (_format_number($amount, $places, numberformat => $numberformat, dash => $dash), $expected, "$comment - explicit"); |
|
19 |
|
|
20 |
{ |
|
21 |
local $::myconfig{numberformat} = $other_numberformat; |
|
22 |
is (_format_number($amount, $places, numberformat => $numberformat, dash => $dash), $expected, "$comment - explicit with different numberformat"); |
|
23 |
} |
|
24 |
{ |
|
25 |
local $::myconfig{numberformat} = $numberformat; |
|
26 |
is (_format_number($amount, $places, dash => $dash), $expected, "$comment - implicit numberformat"); |
|
27 |
} |
|
28 |
|
|
29 |
# test _format_total |
|
30 |
if ($places == 2) { |
|
31 |
is (_format_total($amount, numberformat => $numberformat, dash => $dash), $expected, "$comment - explicit"); |
|
32 |
|
|
33 |
{ |
|
34 |
local $::myconfig{numberformat} = $other_numberformat; |
|
35 |
is (_format_total($amount, numberformat => $numberformat, dash => $dash), $expected, "$comment - explicit with different numberformat"); |
|
36 |
} |
|
37 |
{ |
|
38 |
local $::myconfig{numberformat} = $numberformat; |
|
39 |
is (_format_total($amount, dash => $dash), $expected, "$comment - implicit numberformat"); |
|
40 |
} |
|
41 |
} |
|
42 |
} |
|
43 |
|
|
44 |
|
|
45 |
test_format('10,00', '1e1', 2, '1.000,00', undef, 'format 1e1 (numberformat: 1.000,00)'); |
|
46 |
test_format('1.000,00', 1000, 2, '1.000,00', undef, 'format 1000 (numberformat: 1.000,00)'); |
|
47 |
test_format('1.000,12', 1000.1234, 2, '1.000,00', undef, 'format 1000.1234 (numberformat: 1.000,00)'); |
|
48 |
test_format('1.000.000.000,12', 1000000000.1234, 2, '1.000,00', undef, 'format 1000000000.1234 (numberformat: 1.000,00)'); |
|
49 |
test_format('-1.000.000.000,12', -1000000000.1234, 2, '1.000,00', undef, 'format -1000000000.1234 (numberformat: 1.000,00)'); |
|
50 |
|
|
51 |
test_format('10.00', '1e1', 2, '1,000.00', undef, 'format 1e1 (numberformat: 1,000.00)'); |
|
52 |
test_format('1,000.00', 1000, 2, '1,000.00', undef, 'format 1000 (numberformat: 1,000.00)'); |
|
53 |
test_format('1,000.12', 1000.1234, 2, '1,000.00', undef, 'format 1000.1234 (numberformat: 1,000.00)'); |
|
54 |
test_format('1,000,000,000.12', 1000000000.1234, 2, '1,000.00', undef, 'format 1000000000.1234 (numberformat: 1,000.00)'); |
|
55 |
test_format('-1,000,000,000.12', -1000000000.1234, 2, '1,000.00', undef, 'format -1000000000.1234 (numberformat: 1,000.00)'); |
|
56 |
|
|
57 |
# negative places |
|
58 |
|
|
59 |
test_format('1.00045', 1.00045, -2, '1,000.00', undef, 'negative places'); |
|
60 |
test_format('1.00045', 1.00045, -5, '1,000.00', undef, 'negative places 2'); |
|
61 |
test_format('1.00', 1, -2, '1,000.00', undef, 'negative places 3'); |
|
62 |
|
|
63 |
# bugs amd edge cases |
|
64 |
$config->{numberformat} = '1.000,00'; |
|
65 |
|
|
66 |
test_format('0,00005', 0.00005, undef, '1.000,00', undef, 'messing with small numbers and no precision'); |
|
67 |
test_format('0', undef, undef, '1.000,00', undef, 'undef'); |
|
68 |
test_format('0', '', undef, '1.000,00', undef, 'empty string'); |
|
69 |
test_format('0,00', undef, 2, '1.000,00', undef, 'undef with precision'); |
|
70 |
test_format('0,00', '', 2, '1.000,00', undef, 'empty string with prcesion'); |
|
71 |
|
|
72 |
test_format('1', 0.545, 0, '1.000,00', undef, 'rounding up with precision 0'); |
|
73 |
test_format('-1', -0.545, 0, '1.000,00', undef, 'neg rounding up with precision 0'); |
|
74 |
|
|
75 |
test_format('1', 1.00, undef, '1.000,00', undef, 'autotrim to 0 places'); |
|
76 |
|
|
77 |
test_format('10', 10, undef, '1.000,00', undef, 'autotrim does not harm integers'); |
|
78 |
test_format('10,00', 10, 2, '1.000,00', undef, 'autotrim does not harm integers 2'); |
|
79 |
test_format('10,00', 10, -2, '1.000,00', undef, 'autotrim does not harm integers 3'); |
|
80 |
test_format('10', 10, 0, '1.000,00', undef, 'autotrim does not harm integers 4'); |
|
81 |
|
|
82 |
test_format('0', 0, 0, '1.000,00', undef, 'trivial zero'); |
|
83 |
test_format('0,00', -0.002, 2, '1.000,00', undef, 'negative zero'); |
|
84 |
test_format('-0,002', -0.002, 3, '1.000,00', undef, 'negative zero'); |
|
85 |
|
|
86 |
# dash |
|
87 |
|
|
88 |
test_format('(350,00)', -350, 2, '1.000,00', '-', 'dash -'); |
|
89 |
|
|
90 |
# parse |
|
91 |
|
|
92 |
sub test_parse { |
|
93 |
my ($expected, $amount, $numberformat, $comment) = @_; |
|
94 |
|
|
95 |
my $other_numberformat = $numberformat eq '1.000,00' ? '1,000.00' : '1.000,00'; |
|
96 |
|
|
97 |
is (_parse_number($amount, numberformat => $numberformat), $expected, "$comment - explicit"); |
|
98 |
|
|
99 |
{ |
|
100 |
local $::myconfig{numberformat} = $other_numberformat; |
|
101 |
is (_parse_number($amount, numberformat => $numberformat), $expected, "$comment - explicit with different numberformat"); |
|
102 |
} |
|
103 |
{ |
|
104 |
local $::myconfig{numberformat} = $numberformat; |
|
105 |
is (_parse_number($amount), $expected, "$comment - implicit numberformat"); |
|
106 |
} |
|
107 |
} |
|
108 |
|
|
109 |
|
|
110 |
test_parse(12345, '12345', '1.000,00', '12345 (numberformat: 1.000,00)'); |
|
111 |
test_parse(1234.5, '1.234,5', '1.000,00', '1.234,5 (numberformat: 1.000,00)'); |
|
112 |
test_parse(9871234.5, '9.871.234,5', '1.000,00', '9.871.234,5 (numberformat: 1.000,00)'); |
|
113 |
test_parse(1234.5, '1234,5', '1.000,00', '1234,5 (numberformat: 1.000,00)'); |
|
114 |
test_parse(12345, '012345', '1.000,00', '012345 (numberformat: 1.000,00)'); |
|
115 |
test_parse(1234.5, '01.234,5', '1.000,00', '01.234,5 (numberformat: 1.000,00)'); |
|
116 |
test_parse(1234.5, '01234,5', '1.000,00', '01234,5 (numberformat: 1.000,00)'); |
|
117 |
test_parse(9871234.5, '09.871.234,5', '1.000,00', '09.871.234,5 (numberformat: 1.000,00)'); |
|
118 |
|
|
119 |
# round |
|
120 |
|
|
121 |
is(_round_number('3.231',2),'3.23'); |
|
122 |
is(_round_number('3.234',2),'3.23'); |
|
123 |
is(_round_number('3.235',2),'3.24'); |
|
124 |
is(_round_number('5.786',2),'5.79'); |
|
125 |
is(_round_number('2.342',2),'2.34'); |
|
126 |
is(_round_number('1.2345',2),'1.23'); |
|
127 |
is(_round_number('8.2345',2),'8.23'); |
|
128 |
is(_round_number('8.2350',2),'8.24'); |
|
129 |
|
|
130 |
|
|
131 |
is(_round_total('3.231'),'3.23'); |
|
132 |
is(_round_total('3.234'),'3.23'); |
|
133 |
is(_round_total('3.235'),'3.24'); |
|
134 |
is(_round_total('5.786'),'5.79'); |
|
135 |
is(_round_total('2.342'),'2.34'); |
|
136 |
is(_round_total('1.2345'),'1.23'); |
|
137 |
is(_round_total('8.2345'),'8.23'); |
|
138 |
is(_round_total('8.2350'),'8.24'); |
t/helper/round.t | ||
---|---|---|
1 |
use Test::More tests => 9; |
|
2 |
|
|
3 |
use lib 't'; |
|
4 |
|
|
5 |
use SL::Helper::Number qw(:all); |
|
6 |
|
|
7 |
use_ok 'Support::TestSetup'; |
|
8 |
|
|
9 |
Support::TestSetup::login(); |
|
10 |
|
|
11 |
is(_round_number('3.231',2),'3.23'); |
|
12 |
is(_round_number('3.234',2),'3.23'); |
|
13 |
is(_round_number('3.235',2),'3.24'); |
|
14 |
is(_round_number('5.786',2),'5.79'); |
|
15 |
is(_round_number('2.342',2),'2.34'); |
|
16 |
is(_round_number('1.2345',2),'1.23'); |
|
17 |
is(_round_number('8.2345',2),'8.23'); |
|
18 |
is(_round_number('8.2350',2),'8.24'); |
Auch abrufbar als: Unified diff
SL::Helper::Number: API Verbesserungen
- API ist jetzt einheitlich ($amount, [$places], %params)
- Benennung ist einheitlich [format|parse|round][number|total]
- Tests aus t/helper/round.t nach t/helper/number.t verschoben
- Tests für alle neuen Funktionen hinzugefügt
- Doku-Update
- SL::Form angepasst
- EXPORT_ALL tag ":ALL" auf caps umgestellt