Skip to content

Commit

Permalink
- added absolute floor/ceil to roundoption
Browse files Browse the repository at this point in the history
- floor(), abs_floor(), ceil() and abs_ceil() added
- roundoption integrated as fifth argument to format_number()
- see billward#9
  • Loading branch information
Sadrak committed Mar 7, 2017
1 parent 9226f5e commit 8da1836
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 11 deletions.
112 changes: 103 additions & 9 deletions Format.pm
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,9 @@ positive representation of the number being passed to that function.
C<format_number()> and C<format_price()> utilize this feature by
calling C<format_negative()> if the number was less than 0.
C<ROUND_OPTION> is only used by C<round()>. A number less then 0 means
floor, a number bigger then 0 ceil and a 0 stand for a normal rounding.
C<ROUND_OPTION> is only used by C<round()>. -1 means floor, -2 means
absolute floor, 1 means ceil, 2 means absolute ceil and a 0 stand for
normal rounding.
C<KILO_SUFFIX>, C<MEGA_SUFFIX>, and C<GIGA_SUFFIX> are used by
C<format_bytes()> when the value is over 1024, 1024*1024, or
Expand Down Expand Up @@ -188,7 +189,8 @@ use base qw(Exporter);

our @EXPORT_SUBS =
qw( format_number format_negative format_picture
format_price format_bytes round unformat_number );
format_price format_bytes unformat_number
round floor ceil abs_floor abs_ceil );

our @EXPORT_LC_NUMERIC =
qw( $DECIMAL_POINT $THOUSANDS_SEP $GROUPING );
Expand Down Expand Up @@ -541,10 +543,18 @@ sub round

if ($roundoption) {
$rounded *= 10**$precision;
if ($roundoption == -1) {
if (
$roundoption == -1
or ($roundoption == -2 and not $rounded->is_neg())
or ($roundoption == 2 and $rounded->is_neg())
) {
$rounded->bfloor();
}
elsif ($roundoption == 1) {
elsif (
$roundoption == 1
or ($roundoption == 2 and not $rounded->is_neg())
or ($roundoption == -2 and $rounded->is_neg())
) {
$rounded->bceil();
}
else {
Expand All @@ -568,7 +578,11 @@ sub round

# We need to add 1e-14 to avoid some rounding errors due to the
# way floating point numbers work - see string-eq test in t/round.t
if ($roundoption == -1) {
if (
$roundoption == -1
or ($roundoption == -2 and not $sign < 0)
or ($roundoption == 2 and $sign < 0)
) {
if ($sign < 0) {
$result = int($product + 1 - 1e-14) / -$multiplier;
}
Expand All @@ -584,7 +598,11 @@ sub round
$result = int($product + 0.5 + 1e-14) / $multiplier;
}
}
elsif($roundoption == 1) {
elsif (
$roundoption == 1
or ($roundoption == 2 and not $sign < 0)
or ($roundoption == -2 and $sign < 0)
) {
if ($sign < 0) {
$result = int($product) / -$multiplier;
}
Expand All @@ -601,6 +619,82 @@ sub round

##----------------------------------------------------------------------

=item floor($number, $precision)
Floors the number to the specified precision.
floor(3.14159) yields 3
floor(3.14159, 2) yields 3.14
floor(-42.5) yields -43
=cut

sub floor
{
my ($self, $number, $precision) = _get_self @_;

return $self->round($number, $precision, -1);
}

##----------------------------------------------------------------------

=item abs_floor($number, $precision)
Floors the number to the specified precision.
abs_floor(3.14159) yields 3
abs_floor(3.14159, 2) yields 3.14
abs_floor(-42.5) yields -42
=cut

sub abs_floor
{
my ($self, $number, $precision) = _get_self @_;

return $self->round($number, $precision, -2);
}

##----------------------------------------------------------------------

=item ceil($number, $precision)
Ceils the number to the specified precision.
ceil(3.14159) yields 4
ceil(3.14159, 2) yields 3.15
ceil(-42.5) yields -42
=cut

sub ceil
{
my ($self, $number, $precision) = _get_self @_;

return $self->round($number, $precision, 1);
}

##----------------------------------------------------------------------

=item abs_ceil($number, $precision)
Ceils the number to the specified precision.
ceil(3.14159) yields 4
ceil(3.14159, 2) yields 3.15
ceil(-42.5) yields -4e
=cut

sub abs_ceil
{
my ($self, $number, $precision) = _get_self @_;

return $self->round($number, $precision, 2);
}

##----------------------------------------------------------------------

=item format_number($number, $precision, $trailing_zeroes)
Formats a number by adding C<THOUSANDS_SEP> between each set of 3
Expand Down Expand Up @@ -634,7 +728,7 @@ C<DECIMAL_POINT> instead of ',' and '.' respectively.

sub format_number
{
my ($self, $number, $precision, $trailing_zeroes, $mon) = _get_self @_;
my ($self, $number, $precision, $trailing_zeroes, $mon, $roundoption) = _get_self @_;

unless (defined($number))
{
Expand All @@ -654,8 +748,8 @@ sub format_number

# Handle negative numbers
my $sign = $number <=> 0;
$number = $self->round($number, $precision, $roundoption); # round off $number
$number = abs($number) if $sign < 0;
$number = $self->round($number, $precision); # round off $number

# detect scientific notation
my $exponent = 0;
Expand Down
8 changes: 8 additions & 0 deletions t/bigfloat.t
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,26 @@ SKIP:

my $nf = Number::Format->new();
is($nf->round(Math::BigFloat->new(123.456), 2), '123.46', "round");
is($nf->round(Math::BigFloat->new(1), 0, -2), '1', "abs floor");
is($nf->round(Math::BigFloat->new(1), 0, -1), '1', "floor");
is($nf->round(Math::BigFloat->new(1), 0, 0), '1', "normal");
is($nf->round(Math::BigFloat->new(1), 0, +1), '1', "ceil");
is($nf->round(Math::BigFloat->new(1), 0, +2), '1', "abs ceil");
is($nf->round(Math::BigFloat->new(1.1), 0, -2), '1', "abs floor");
is($nf->round(Math::BigFloat->new(1.1), 0, -1), '1', "floor");
is($nf->round(Math::BigFloat->new(1.1), 0, 0), '1', "normal");
is($nf->round(Math::BigFloat->new(1.1), 0, +1), '2', "ceil");
is($nf->round(Math::BigFloat->new(1.1), 0, +2), '2', "abs ceil");
is($nf->round(Math::BigFloat->new(1.15), 1, -2), '1.1', "abs floor");
is($nf->round(Math::BigFloat->new(1.15), 1, -1), '1.1', "floor");
is($nf->round(Math::BigFloat->new(1.15), 1, 0), '1.2', "normal");
is($nf->round(Math::BigFloat->new(1.15), 1, +1), '1.2', "ceil");
is($nf->round(Math::BigFloat->new(1.15), 1, +2), '1.2', "abs ceil");
is($nf->round(Math::BigFloat->new(-1.1), 0, -2), '-1', "abs floor");
is($nf->round(Math::BigFloat->new(-1.1), 0, -1), '-2', "floor");
is($nf->round(Math::BigFloat->new(-1.1), 0, 0), '-1', "normal");
is($nf->round(Math::BigFloat->new(-1.1), 0, +1), '-1', "ceil");
is($nf->round(Math::BigFloat->new(-1.1), 0, +2), '-2', "abs ceil");
is($nf->format_number(Math::BigFloat->new(500.27), 2, 1), '500.27');
is($nf->format_number(Math::BigFloat->bpi(100), 7, 1), '3.1415927');
is($nf->format_price(Math::BigFloat->bpi(100), 4, "\$"), '$ 3.1416');
Expand Down
10 changes: 10 additions & 0 deletions t/format_number.t
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ is(format_number(1.23456789, 6), '1.234568', 'six digit rounding');
is(format_number('1.2300', 7, 1), '1.2300000', 'extra zeroes');
is(format_number(.23, 7, 1), '0.2300000', 'leading zero');
is(format_number(-100, 7, 1), '-100.0000000', 'negative with zeros');
is(format_number(1.1, 0, undef, undef, -2), '1', 'abs floor');
is(format_number(1.1, 0, undef, undef, -1), '1', 'floor');
is(format_number(1.1, 0, undef, undef, 0), '1', 'round');
is(format_number(1.1, 0, undef, undef, +1), '2', 'ceil');
is(format_number(1.1, 0, undef, undef, +2), '2', 'abs ceil');
is(format_number(-1.6, 0, undef, undef, -2), '-1', 'abs floor');
is(format_number(-1.6, 0, undef, undef, -1), '-2', 'floor');
is(format_number(-1.6, 0, undef, undef, 0), '-2', 'round');
is(format_number(-1.6, 0, undef, undef, +1), '-1', 'ceil');
is(format_number(-1.6, 0, undef, undef, +2), '-2', 'abs ceil');

is(format_number("0.000020000E+00", 7), '2e-05', 'scientific notation not processed');

Expand Down
30 changes: 28 additions & 2 deletions t/round.t
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use Test::More qw(no_plan);
use strict;
use warnings;

use POSIX;
setlocale(&LC_ALL, 'C');
use POSIX qw();
POSIX::setlocale(&POSIX::LC_ALL, 'C');

BEGIN { use_ok('Number::Format', ':subs') }

Expand All @@ -14,21 +14,31 @@ use constant PI => 4*atan2(1,1);
ok(compare_numbers(round(0), 0), 'identity 0');
ok(compare_numbers(round(1), 1), 'identity 1');
ok(compare_numbers(round(-1), -1), 'identity -1');
ok(compare_numbers(round(1,0,-2), 1), 'abs floor 1');
ok(compare_numbers(round(1,0,-1), 1), 'floor 1');
ok(compare_numbers(round(1,0, 0), 1), 'normal 1');
ok(compare_numbers(round(1,0,+1), 1), 'ceil 1');
ok(compare_numbers(round(1,0,+2), 1), 'abs ceil 1');
ok(compare_numbers(round(-1,0,-2), -1), 'abs floor -1');
ok(compare_numbers(round(-1,0,-1), -1), 'floor -1');
ok(compare_numbers(round(-1,0, 0), -1), 'normal -1');
ok(compare_numbers(round(-1,0,+1), -1), 'ceil -1');
ok(compare_numbers(round(-1,0,+2), -1), 'abs ceil -1');
ok(compare_numbers(round(1.1,0,-2), 1), 'abs floor 1.1');
ok(compare_numbers(round(1.1,0,-1), 1), 'floor 1.1');
ok(compare_numbers(round(1.1,0, 0), 1), 'normal 1.1');
ok(compare_numbers(round(1.1,0,+1), 2), 'ceil 1.1');
ok(compare_numbers(round(1.1,0,+2), 2), 'abs ceil 1.1');
ok(compare_numbers(round(-1.1,0,-2), -1), 'abs floor -1.1');
ok(compare_numbers(round(-1.1,0,-1), -2), 'floor -1.1');
ok(compare_numbers(round(-1.1,0, 0), -1), 'normal -1.1');
ok(compare_numbers(round(-1.1,0,+1), -1), 'ceil -1.1');
ok(compare_numbers(round(-1.1,0,+2), -2), 'abs ceil -1.1');
ok(compare_numbers(round(-1.6,0,-2), -1), 'abs floor -1.6');
ok(compare_numbers(round(-1.6,0,-1), -2), 'floor -1.6');
ok(compare_numbers(round(-1.6,0, 0), -2), 'normal -1.6');
ok(compare_numbers(round(-1.6,0,+1), -1), 'ceil -1.6');
ok(compare_numbers(round(-1.6,0,+2), -2), 'abs ceil -1.6');
ok(compare_numbers(round(PI,2), 3.14), 'pi prec=2');
ok(compare_numbers(round(PI,3), 3.142), 'pi prec=3');
ok(compare_numbers(round(PI,4), 3.1416), 'pi prec=4');
Expand All @@ -41,6 +51,22 @@ ok(compare_numbers(round(-12345678.5, 2), -12345678.5), 'negative tenths' );
ok(compare_numbers(round(-123456.78951, 4), -123456.7895), 'precision=4' );
ok(compare_numbers(round(123456.78951, -2), 123500), 'precision=-2' );

ok(compare_numbers(abs_floor(PI,0), 3), 'absolute floor pi prec=0');
ok(compare_numbers(abs_floor(PI,2), 3.14), 'absolute floor pi prec=2');
ok(compare_numbers(abs_floor(-42.5,0), -42), 'absolute floor negative');

ok(compare_numbers(floor(PI,0), 3), 'floor pi prec=0');
ok(compare_numbers(floor(PI,2), 3.14), 'floor pi prec=2');
ok(compare_numbers(floor(-42.5,0), -43), 'floor negative');

ok(compare_numbers(ceil(PI,0), 4), 'ceil pi prec=0');
ok(compare_numbers(ceil(PI,2), 3.15), 'ceil pi prec=2');
ok(compare_numbers(ceil(-42.5,0), -42), 'ceil negative');

ok(compare_numbers(abs_ceil(PI,0), 4), 'absolute ceil pi prec=0');
ok(compare_numbers(abs_ceil(PI,2), 3.15), 'absolute ceil pi prec=2');
ok(compare_numbers(abs_ceil(-42.5,0), -43), 'absolute ceil negative');
#
# Without the 1e-10 "epsilon" value in round(), the floating point
# number math will result in 1 rather than 1.01 for this test.
is(round(1.005, 2), 1.01, 'string-eq' );
Expand Down

0 comments on commit 8da1836

Please sign in to comment.