package Number::FormatEng;
use warnings;
use strict;
use Carp qw(croak);
use POSIX qw(floor);
use Scalar::Util qw(looks_like_number);
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(format_eng format_pref unformat_pref use_e_zero no_e_zero);
our %EXPORT_TAGS = (all => \@EXPORT_OK);
our $VERSION = '0.03';
my %prefix = (
'-8' => 'y', '8' => 'Y',
'-7' => 'z', '7' => 'Z',
'-6' => 'a', '6' => 'E',
'-5' => 'f', '5' => 'P',
'-4' => 'p', '4' => 'T',
'-3' => 'n', '3' => 'G',
'-2' => 'u', '2' => 'M',
'-1' => 'm', '1' => 'k',
'0' => '' ,
);
my %exponent = reverse %prefix;
my $no_e_zero = 1;
sub use_e_zero {
$no_e_zero = 0;
}
sub no_e_zero {
$no_e_zero = 1;
}
sub format_pref {
return format_num(1, @_);
}
sub format_eng {
return format_num(0, @_);
}
sub format_num {
my $prefix_mode = shift;
my $num = shift;
my $name = ($prefix_mode) ? 'format_pref' : 'format_eng';
# Check validity of input
unless (defined $num) {
croak("Error: $name requires numeric input. ",
'It seems like no input was provided or input was undefined');
}
unless (looks_like_number($num)) {
croak("Error: $name requires numeric input. '$num' is not numeric");
}
if ($num == 0) {
if ($prefix_mode or $no_e_zero) {
return '0';
}
else {
return '0e0';
}
}
my $sign = ($num < 0) ? '-' : '';
$num = abs $num;
if ($prefix_mode) {
if ( ($num >= 1e27) or ($num <= 1e-25) ) {
# switch to number exponent mode
$prefix_mode = 0;
}
}
my $e = floor( log($num) / log(1000) );
my $mult = 1000**$e;
$num = 0 + sprintf '%.6f', ($num / $mult);
if ($prefix_mode) {
return $sign . $num . $prefix{$e};
}
else {
if ($no_e_zero and ($e == 0)) {
return $sign . $num;
}
else {
return $sign . $num . 'e' . 3*$e;
}
}
}
sub unformat_pref {
my ($num) = @_;
# Check validity of input
unless (defined $num) {
croak('Error: unformat_pref requires input. ',
'It seems like no input was provided or input was undefined');
}
# Trim leading and trailing whitespace
$num =~ s/^\s+//;
$num =~ s/\s+$//;
unless (length $num) {
croak('Error: unformat_pref requires input. ',
'It seems like no input was provided');
}
my $prefix = substr $num, -1;
if (exists $exponent{$prefix}) {
chop $num;
unless (looks_like_number($num)) {
croak("Error: unformat_pref input '$num' is not numeric before prefix '$prefix'");
}
$num = $num * (1000**$exponent{$prefix});
}
else {
unless (looks_like_number($num)) {
croak("Error: unformat_pref input '$num' is not numeric");
}
}
return $num;
}
=head1 NAME
Number::FormatEng - Format a number using engineering notation
=head1 VERSION
This document refers to Number::FormatEng version 0.03.
=head1 SYNOPSIS
use Number::FormatEng qw(:all);
print format_eng(1234); # prints 1.234e3
print format_pref(-0.035); # prints -35m
unformat_pref('1.23T'); # returns 1.23e+12
=head1 DESCRIPTION
Format a number for printing using engineering notation.
Engineering notation is similar to scientific notation except that
the power of ten must be a multiple of three.
Alternately, the number can be formatted using an International
System of Units (SI) prefix representing a factor of a thousand.
=head1 SUBROUTINES
=over 4
=item format_eng($number)
Format a numeric value using engineering notation. This function
returns a string whose exponent is a multiple of 3. Here are some examples:
format_eng(1234); # returns 1.234e3
format_eng(-0.03); # returns -30e-3
format_eng(7.8e7); # returns 78e6
Since floating-point arithmetic is performed, rounding may occur.
=item format_pref($number)
Format a numeric value using engineering notation. This function
returns a string using one of the following SI prefixes (representing a
power of a thousand):
m u n p f a z y
k M G T P E Z Y
Notice that lower-case C<u> is used instead of the Greek letter Mu.
If the number is beyond the prefix ranges (y and Y), then C<format_pref>
returns the same formatted string as C<format_eng>. In other words, it
does not use an SI prefix.
Here are some examples:
format_pref(1234); # returns 1.234k
format_pref(-0.0004); # returns -400u
format_pref(1.27e13); # returns 12.7T
format_pref(7.5e60); # returns 7.5e60
Since floating-point arithmetic is performed, rounding may occur.
=item unformat_pref($string)
Convert a string formatted using C<format_pref> into a numeric value.
Here are some examples:
unformat_pref('1.23T'); # returns 1.23e+12
unformat_pref('-400u'); # returns -4e-4
unformat_pref(37.5); # returns 37.5
=item use_e_zero() and no_e_zero()
By default, if the exponent is zero, C<e0> is not displayed by
C<format_eng>. To explicitly display C<e0>, use the C<use_e_zero> method.
Use the C<no_e_zero> method to return to the default behavior.
format_eng(55); # returns 55
Number::FormatEng::use_e_zero();
format_eng(55); # now returns 55e0
Number::FormatEng::no_e_zero();
format_eng(55); # back to 55
=back
=head1 EXPORT
Nothing is exported by default. Functions may be exported individually, or
all functions may be exported at once, using the special tag C<:all>.
=head1 DIAGNOSTICS
Error conditions cause the program to die using C<croak> from the
L<Carp|Carp> Core module.
=head1 BUGS AND LIMITATIONS
There are no known bugs in this module.
=head1 SEE ALSO
Refer to the following website:
L<http://en.wikipedia.org/wiki/Engineering_notation>
=head1 AUTHOR
Gene Sullivan (gsullivan@cpan.org)
=head1 ACKNOWLEDGEMENTS
Influenced by the following PerlMonks: BrowserUk, GrandFather and repellent.
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2009 Gene Sullivan. All rights reserved.
This module is free software; you can redistribute it and/or modify
it under the same terms as Perl itself. See L<perlartistic>.
=cut
1;