Output of perldoc -q round
Does Perl have a round() function? What about ceil() and floor()?
Trig functions?
Remember that int()
merely truncates toward 0
. For rounding to a certain number of digits, sprintf()
or printf()
is usually the easiest
route.
printf("%.3f", 3.1415926535); # prints 3.142
The POSIX
module (part of the standard Perl distribution) implements
ceil()
, floor()
, and a number of other mathematical and trigonometric
functions.
use POSIX;
$ceil = ceil(3.5); # 4
$floor = floor(3.5); # 3
In 5.000 to 5.003 perls, trigonometry was done in the Math::Complex
module. With 5.004, the Math::Trig
module (part of the standard Perl
distribution) implements the trigonometric functions. Internally it
uses the Math::Complex
module and some functions can break out from the
real axis into the complex plane, for example the inverse sine of 2.
Rounding in financial applications can have serious implications, and
the rounding method used should be specified precisely. In these
cases, it probably pays not to trust whichever system rounding is being
used by Perl, but to instead implement the rounding function you need
yourself.
To see why, notice how you'll still have an issue on half-way-point
alternation:
for ($i = 0; $i < 1.01; $i += 0.05) { printf "%.1f ",$i}
0.0 0.1 0.1 0.2 0.2 0.2 0.3 0.3 0.4 0.4 0.5 0.5 0.6 0.7 0.7
0.8 0.8 0.9 0.9 1.0 1.0
Don't blame Perl. It's the same as in C. IEEE says we have to do
this. Perl numbers whose absolute values are integers under 2**31
(on
32 bit machines) will work pretty much like mathematical integers.
Other numbers are not guaranteed.
This can't be done with the normal printf
format specifiers. The closest you could get would be:
printf("%.6g", 359.013); // 359.013
printf("%.6g", 359.01); // 359.01
but the ".6" is the total numeric width so
printf("%.6g", 3.01357); // 3.01357
breaks it.
What you can do is to sprintf("%.20g")
the number to a string buffer then manipulate the string to only have N characters past the decimal point.
Assuming your number is in the variable num, the following function will remove all but the first N
decimals, then strip off the trailing zeros (and decimal point if they were all zeros).
char str[50];
sprintf (str,"%.20g",num); // Make the number.
morphNumericString (str, 3);
: :
void morphNumericString (char *s, int n) {
char *p;
int count;
p = strchr (s,'.'); // Find decimal point, if any.
if (p != NULL) {
count = n; // Adjust for more or less decimals.
while (count >= 0) { // Maximum decimals allowed.
count--;
if (*p == '\0') // If there's less than desired.
break;
p++; // Next character.
}
*p-- = '\0'; // Truncate string.
while (*p == '0') // Remove trailing zeros.
*p-- = '\0';
if (*p == '.') { // If all decimals were zeros, remove ".".
*p = '\0';
}
}
}
If you're not happy with the truncation aspect (which would turn 0.12399
into 0.123
rather than rounding it to 0.124
), you can actually use the rounding facilities already provided by printf
. You just need to analyse the number before-hand to dynamically create the widths, then use those to turn the number into a string:
#include <stdio.h>
void nDecimals (char *s, double d, int n) {
int sz; double d2;
// Allow for negative.
d2 = (d >= 0) ? d : -d;
sz = (d >= 0) ? 0 : 1;
// Add one for each whole digit (0.xx special case).
if (d2 < 1) sz++;
while (d2 >= 1) { d2 /= 10.0; sz++; }
// Adjust for decimal point and fractionals.
sz += 1 + n;
// Create format string then use it.
sprintf (s, "%*.*f", sz, n, d);
}
int main (void) {
char str[50];
double num[] = { 40, 359.01335, -359.00999,
359.01, 3.01357, 0.111111111, 1.1223344 };
for (int i = 0; i < sizeof(num)/sizeof(*num); i++) {
nDecimals (str, num[i], 3);
printf ("%30.20f -> %s\n", num[i], str);
}
return 0;
}
The whole point of nDecimals()
in this case is to correctly work out the field widths, then format the number using a format string based on that. The test harness main()
shows this in action:
40.00000000000000000000 -> 40.000
359.01335000000000263753 -> 359.013
-359.00999000000001615263 -> -359.010
359.00999999999999090505 -> 359.010
3.01357000000000008200 -> 3.014
0.11111111099999999852 -> 0.111
1.12233439999999995429 -> 1.122
Once you have the correctly rounded value, you can once again pass that to morphNumericString()
to remove trailing zeros by simply changing:
nDecimals (str, num[i], 3);
into:
nDecimals (str, num[i], 3);
morphNumericString (str, 3);
(or calling morphNumericString
at the end of nDecimals
but, in that case, I'd probably just combine the two into one function), and you end up with:
40.00000000000000000000 -> 40
359.01335000000000263753 -> 359.013
-359.00999000000001615263 -> -359.01
359.00999999999999090505 -> 359.01
3.01357000000000008200 -> 3.014
0.11111111099999999852 -> 0.111
1.12233439999999995429 -> 1.122
Best Answer
Use the following directly:
...or define a subroutine to do it more generically:
Prints: