diff --git a/Format.pm b/Format.pm index 6658bc1..da216d4 100755 --- a/Format.pm +++ b/Format.pm @@ -129,8 +129,9 @@ positive representation of the number being passed to that function. C and C utilize this feature by calling C if the number was less than 0. -C is only used by C. A number less then 0 means -floor, a number bigger then 0 ceil and a 0 stand for a normal rounding. +C is only used by C. -1 means floor, -2 means +absolute floor, 1 means ceil, 2 means absolute ceil and a 0 stand for +normal rounding. C, C, and C are used by C when the value is over 1024, 1024*1024, or @@ -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 ); @@ -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 { @@ -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; } @@ -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; } @@ -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 between each set of 3 @@ -634,7 +728,7 @@ C 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)) { @@ -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; diff --git a/t/bigfloat.t b/t/bigfloat.t index 63bf611..d71d6cb 100755 --- a/t/bigfloat.t +++ b/t/bigfloat.t @@ -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'); diff --git a/t/format_number.t b/t/format_number.t index 2e752d6..66a61a0 100755 --- a/t/format_number.t +++ b/t/format_number.t @@ -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'); diff --git a/t/round.t b/t/round.t index 70a9937..19ce0d6 100755 --- a/t/round.t +++ b/t/round.t @@ -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') } @@ -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'); @@ -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' );