package ScaleUnits; $VERSION = '0.01'; use strict; use warnings; use Carp; my @si_prefixes = qw(kilo mega giga tera peta exa zetta yotta); sub new { my $class = shift; my %args = @_; croak "ScaleUnits::new requires named parameter 'unit'\n" unless defined $args{unit}; %args = (plural => $args{unit} . 's', factor => 1000, round => 0, brief => '', separator => ' ', %args); my $self = \%args; bless $self, $class; return $self; } sub scale { my ($self, $value) = @_; return scale_units($value, $self->{unit}, $self->{plural}, $self->{factor}, $self->{round}, $self->{brief}, $self->{separator}); } sub scale_units { my ($value, $base_unit, $plural, $factor, $round, $brief, $separator) = @_; my $order = 0; while ($value >= $factor && $order <= $#si_prefixes) { $value /= $factor; $order++; } $value = int($value*(10**$round))/(10**$round); # if $order is 0, the number doesn't need a prefix # otherwise it needs a prefix. by default ($brief is ''), we use the # full SI prefix. If $brief contains something, we instead use the short # and capitalized form of the SI prefix (e.g. 'mega' becomes 'M'). my $si_prefix = ($order == 0 ? '' : $brief eq '' ? $si_prefixes[$order-1] : uc substr($si_prefixes[$order-1],0,1)); # if $brief is set and we do have a prefix ($order >0), then use $brief # as the units. Otherwise we have to decide between singular and plural. my $units = ($brief ne '' && $order > 0 ? $brief : $value == 1 ? $base_unit : $plural); return "$value$separator$si_prefix$units"; } 1; __END__ =head1 NAME ScaleUnits - Perl class that takes numbers and formats them nicely, with SI prefixes, user-selectable rounding, and more =head1 SYNOPSIS use ScaleUnits; $converter = ScaleUnits->new( unit => 'byte', factor => 1024, round => 2 ); print $converter->scale(123_456_789); # '117.73 megabytes' =head1 DESCRIPTION You want your program to generate nice readable output like this: Transferred 12 bytes Transferred 2.14 gigabytes instead of this: Transferred 12 byte(s) Transferred 2243953 byte(s) Specifically, C can apply SI prefixes from 'kilo' (10**3, or 2**10 if you wish) through 'yotta' (10**24). It can round numbers to the number of decimal places that you specify, and apply singular and plural forms as appropriate. It can also use abbreviated forms such as 'MB' and 'GB'. You define the desired behavior when you construct a C object, and then each time you call the C method with a specific number, the appropriate rules are applied and a nicely-formatted string is returned. =head1 PUBLIC METHODS =head2 new(%params) Create a new C object. Pass parameters by name. A C parameter is required; others are optional. =over 4 =item unit The name of the units you are scaling. Use the singular form: 'byte', 'minute', and so on. By default, C will construct a plural form by adding an 's' to this base unit, but you can override this behavior by passing a C argument. =item plural The plural of the units you are scaling. By default, C creates the plural form of a unit by appending a single 's'. =item factor The factor you want to use between successive levels of the SI prefix hierarchy. By default, this is 1,000. For scaling bytes and other base-2 numbers, you may want to make it 1,024: $s = ScaleUnits->new(unit => 'byte', factor => 1024); Now 'kilo' will be used for a factor of 1,024, 'mega' for a factor of 1,048,976, and so on. =item round Round scaled numbers to this many decimal places (maximum). By default, it's 0, and all results will be in whole numbers. =item brief Set C to the units you want to use when a number is large enough to require an SI prefix. This will also cause the single-letter form of SI prefixes to be used. A typical use would be: ScaleUnits->new(unit => 'byte', brief => 'B'); This will result in output like this: 35 bytes 1 MB 4 GB By default, C is a null string, which means that the full SI prefixes are prepended onto the unit name (singular or plural as needed). =item separator Defines the separator that appears between the scaled number and its accompanying units. By default, it's a space, so results will read like '24 kilobytes'. By setting C to a null string you can get results like '24KB' (no space). Setting C to a hyphen could be useful for generating adjectival forms ('You uploaded a 24-kilobyte file'). =back =head2 scale($number) Scale C<$number> using the settings defined when the object was constructed, and return the results as a string. =head1 SEE ALSO If you want to do conversions between different units -- e.g., meters to inches -- you'll probably want to use a module like C or C instead. If you want to handle singular and plural forms for arbitrary terms, try C. =head1 BUGS AND LIMITATIONS Currently provides prefixes only for positive exponents -- there's no provision for converting a fractional number into milli-units, micro-units, and so on. Doesn't currently provide a provision for using the alternate SI prefixes, such as 'kibi', that I've read have been proposed for base-2 numbers. =head1 RELEASE AND REVISION HISTORY 0.01 - 4 October 2002 - first release =head1 AUTHOR AND COPYRIGHT INFORMATION (C) Copyright 2002 John Clyman (Z<>module-support@clyman.comZ<>, http://www.clyman.comZ<>). All Rights Reserved. This program is free software and may be used and redistributed under the terms of the Artistic License. =cut