Number::WithError - Numbers with error propagation and scientific rounding
use Number::WithError; my $num = Number::WithError->new(5.647, 0.31); print $num . "\n"; # prints '5.65e+00 +/- 3.1e-01' # (I.e. it automatically does scientific rounding) my $another = $num * 3; print $another . "\n"; # propagates the error assuming gaussian errors # prints '1.69e+01 +/- 9.3e-01' # trigonometric functions also work: print sin($another) . "\n"; # prints '-9.4e-01 +/- 3.1e-01' my $third = $another ** $num; print $third. "\n"; # propagates both errors into one. # prints '8.7e+06 +/- 8.1e+06' # shortcut for the constructor: use Number::WithError 'witherror'; $num = witherror('0.00032678', ['2.5e-5', '3e-5'], 5e-6); # can deal with any number of errors, even with asymmetric errors print $num . "\n"; # prints '3.268e-04 + 2.5e-05 - 3.00e-05 +/- 5.0e-06' # Note: It may be annyoing that they don't all have the same # exponent, but they *do* all have the sam significant digit!
This class is a container class for numbers with a number of associated symmetric and asymmetric errors. It overloads practically all common arithmetic operations and trigonometric functions to propagate the errors. It can do proper scientific rounding (as explained in more detail below in the documentation of the
You can use Math::BigFloat objects as the internal representation of numbers in order to support arbitrary precision calculations.
Errors are propagated using Gaussian error propagation.
With a notable exception, the test suite covers way over ninety percent of the code. The remaining holes are mostly difficult-to-test corner cases and sanity tests. The comparison routines are the exception for which there will be more extensive tests in a future release.
This module uses overload to enable the use of the ordinary Perl arithmetic operators on objects. All overloaded operations are also availlable via methods. Here is a list of overloaded operators and the equivalent methods. The assignment forms of arithmetic operators (e.g.
+=) are availlable if their normal counterpart is overloaded.
$x + $yimplemented by the
$x++implemented by the
$x - $yimplemented by the
$x--implemented by the
$x * $yimplemented by the
$x / $yimplemented by the
$x ** $yimplemented by the
sin($x)implemented by the
cos($x)implemented by the
"$x"is implemented by the
-$xis implemented by the
abs($x)is implemented via
log($x)is implemented via
sqrt($x)is implemented via
$x == $y,
$x != $y, etc. are implemented via
$x eq $y,
$x ne $y, etc. are implemented via
$x-$<gtfull_cmp($y)>. They might not do what you expect. Please read the documentation.
Here's a list of overloadable operations that aren't overloaded in the context of this module:
<< >> x . & ^ | atan2 int
All constructors accept Math::BigFloat objects in place of numbers.
This is the basic constructor for
New objects can be created in one of two ways:
undef. In the former case, the number is treated as an uncertainty which has the same magnitude in both directions. (I.e.
+/- x) In case of an array reference, the first number is treated as the upper error boundary and the second as the lower boundary. (I.e.
undefis treated as zero error.
133.14e-5 +/- .1e-4 + 0.00002 - 1.0e-5 +/- .2e-4
In this example, the first number is parsed as the actual number. The following number is treated as a symmetric error (
.1e-4) The two following numbers are treated as the upper and lower boundary for a single error. Then comes another ordinary error. It is also legal to define the lower boundary of an error before the upper boundary. (I.e.
Whitespace is insignificant.
For the sake of completeness, I'll mention the regular expression that is used to match numbers. It's taken from the official Perl FAQ to match floating point numbers:
Don't worry if you don't understand it. Just run a few tests to see if it'll accept your numbers. Or go read
perldoc -q float or pick up a book on C and read up on how they define floating point numbers.
Note that trailing zeros currently have no effect. (Yes, this is a BUG!)
The constructor returns a new object of this class or undef if something went wrong.
This is an alternative constructor for
Number::WithError objects. It works exactly like
new except that it makes all internal numbers instances of
Math::BigFloat for high precision calculations.
The module does not load
Math::BigFloat at compile time to avoid loading a big module that isn't needed all the time. Instead, this module makes use of the prefork pragma and loads
Math::BigFloat when needed at run-time.
This constructor is not a method. It is a subroutine that can be exported to your namespace on demand. It works exactly as the
new() method except it's a subroutine and shorter.
I'm normally not for this kind of shortcut in object-oriented code, but if you have to create a large number of
Number::WithError numbers, you'll appreciate it. Trust me.
Note to authors of subclasses: If you inherit from this module, you'll need to implement your own
witherror() because otherwise, it will still return objects of this class, not your subclass.
This is also not a method. It does the same as
witherror(). It can also be optionally be exported to your namespace.
It uses the
new_big constructor instead of the
new constructor used by
All of these methods implement an arithmetic operation on the object and the method's first parameter.
The methods aren't mutators. That means they don't modify the object itself, but return the result of the operation as a new object.
All of the methods accept either a plain number, a
Number::WithError object or anything that is understood by the constructors as argument,
All errors are correctly propagated using Gaussian Error Propagation. The formulae used for this are mentioned in the individual methods' documentation.
Adds the object a and the argument b. Returns a new object c.
c = a + b
err_c = sqrt( err_a^2 + err_b^2 )
Subtracts the argument b from the object a. Returns a new object c.
c = a - b
err_c = sqrt( err_a^2 + err_b^2 )
Multiplies the object a and the argument b. Returns a new object c.
c = a * b
err_c = sqrt( b^2 * err_a^2 + a^2 * err_b^2 )
Divides the object a by the argument b. Returns a new object c.
c = a / b
err-c = sqrt( err_a^2 / b^2 + a^2 * err_b^2 / b^4 )
Raises the object a to the power of the argument b. Returns a new object c. Returns
undef if a is negative because the error cannot be propagated in that case. (Can't take log of a negative value.)
Also, please have a look at the error propagation formula below. Exponentiation and logarithms are operations that can become numerically unstable very easily.
c = a ^ b
err-c = sqrt( b^2 * a^(b-1) * err_a^2 + ln(a)^2 * a^b * err_b^2 )
These methods calculate functions of the object and return the result as a new object.
Calculates the square root of the object a and returns the result as a new object c. Returns undef if a is negative.
c = sqrt(a)
err-c = sqrt( err-a^2 / (2*sqrt(a))^2 ) = abs( err-a / (2*sqrt(a)) )
Calculates the natural logarithm of an object a. Returns a new object c. If a is negative, the function returns undef.
c = log(a)
err-c = sqrt( err-a^2 / a^2 ) = abs( err-a / a )
Calculates the sine of the object a and returns the result as a new object c.
c = sin(a)
err-c = sqrt( cos(a)^2 * err-a^2 ) = abs( cos(a) * err-a )
Calculates the cosine of the object a and returns the result as a new object c.
c = cos(a)
err-c = sqrt( sin(a)^2 * err-a^2 ) = abs( sin(a) * err-a )
Calculates the tangent of the object a and returns the result as a new object c.
c = tan(a)
err-c = sqrt( err-a^2 / cos(a)^4 ) = abs( err-a / cos(a)^2 )
Since there is no built-in
tan() function, this operation is not available via the overloaded interface.
Calculates the absolute value of an object a. Leaves the errors untouched. Returns a new object c.
c = abs(a)
err-c = err-a
This section documents methods dealing with the extraction of data from the object. The methods implement rounding of numbers, stringification of the object and extracting meta information like the significant digit.
Determines the significant digit using the
significant_digit() method, rounds the number that the object
number() is called on represents to that digit and returns the rounded number.
Regardless of the internal representation of the number, this returns an unblessed string / an unblessed floating point number.
To gain access to the raw number representation in the object, use the
Either way, the number will be in scientific notation. That means the first non-zero digit comes before the decimal point and following the decimal point and any number of digits is an exponent in
This method returns the internal representation of the number in the object. It does not round as appropriate. It does not clone
Math::BigFloat objects either. So make sure you do that if necessary!
This method determines the significant digit using the
significant_digit() method. Then, it rounds the number represented by the object and all associated errors to that digit.
Then, the method concatenates the number with its errors and returns the resulting string. In case of symmetric errors, the string
+/- will be prepended to the error. In case of asymmetric errors, a
+ will be prepended to the first/upper error component and a
- to the second/lower error component.
Returns the previously described string.
This method returns the significant digit of the number it is called on as an integer. If the number has no errors or all errors are
undef or zero, this method returns
The return value of this method is to be interpreted as follows: If this method returns
-5, the significant digit is
1 * 10**-5 or
0.00001. If it returns
3, the significant digit is
1 * 10**3 or
1000. If it returns
0, the significant digit is
The return value is computed by the following algorithm: The individual significant digit of a single error is: Take the exponent of the first non-zero digit in the error. The digit after this first non-zero digit is the significant one.
This method returns the minimum of the individual significant digits of all errors.
5 +/- 0.0132 + 0.5 - 1
Will yield a return value of
-3 since the first error has the lowest significant digit.
This algorithm is also used for determining the significant digit for rounding. It is extremely important that you realize this isn't carved in stone. The way the significant digit is computed in the presence of errors is merely a convention. In this case, it stems from particle physics. It might well be that in your particular scientific community, there are other conventions. One, for example, is to use the second non-zero digit only if the first is a 1.
This method returns a reference to an array of errors of the object it is called on.
raw_error() method, this method takes proper care to copy all objects and references to defy action at a distance. The structure of the returned array reference is akin to that returned by
Furthermore, this method rounds all errors to the significant digit as determined by
Returns the internal representation of the errors of the current object. Note that (just like
raw_number(), this does not clone the data for safe use without action at a distance. Instead, it directly returns the internal reference to the error structure. The structure is an array of errors. Each error may either be a string or floating point number or a
Math::BigFloat object or an array reference. In case of an array reference, it is an asymmetric error. The inner array contains two strings/numbers/
Note that this practically breaks encapsulation and code relying on it might break with future releases.
This method returns the information stored in the object as an array (i.e. a list in this context) which can be passed to the
new() method to recreate the object.
The first element of the return list will be the number itself. If the object uses
Math::BigFloat for the internal representation, this element will be a copy of the internal object. Otherwise, it will be the internal representation of the number with full precision.
Following the number will be all errors either as numbers,
Math::BigFloat objects or arrays containing two asymmetric errors. (Either as numbers or objects as explained above.) The data returned by this method will be copied deeply before being returned.
This is a helper function which can round a number to the specified significant digit (defined as the return value of the
my $rounded = round_a_number(12.01234567, -3); # $rounded is now 1.2012e01
This section lists methods that implement different comparisons between objects.
This method implements a numeric comparison of two numbers. It compares the object it is called on to the first argument of the method. If the first argument is omitted or undefined, the method returns
Numeric comparison means in this case that the represented numbers will be rounded and then compared. If you would like a comparison that takes care of errors, please have a look at the
The method returns
-1 if the rounded number represented by the object is numerically less than the rounded number represented by the first argument. It returns
0 if they are equal and
1 if the object's rounded number is more than that of the argument.
This method implements the overloaded numeric comparison operations.
This method implements a full comparison of two objects. That means, it takes their numeric values, rounds them and compares them just like the
If, however, the numbers are equal, this method iterates over the errors, rounds them and then compares them. If all errors are equal, this method returns
0. If an error is found to differ, the method returns
1 in case the object's error is larger and
-1 in case the argument's error is larger.
Comparing an asymmetric error to a symmetric error is a special case. It can never be the same error, hence the method will not return
0. Instead, it guesses which error is larger by using the upper error bound of the asymmetric error. (Well, yes, not very useful.)
Bugs should be reported via the CPAN bug tracker at
For other issues, contact the author.
You may use Math::BigFloat with this module. Also, it should be possible to use Math::Symbolic to calculate larger formulas. Just assign a
Number::WithError object to the
Math::Symbolic variables and it should work.
You also possibly want to have a look at the prefork pragma.
The test suite is implemented using the Test::LectroTest module. In order to keep the total test time in reasonable bounds, the default number of test attempts to falsify the test properties is kept at a low number of 100. You can enable more rigorous testing by setting the environment variable
PERL_TEST_ATTEMPTS to a higher value. A value in the range of
3000 is probably a good idea, but takes a long time to test.
Steffen Mueller <firstname.lastname@example.org>, http://steffen-mueller.net/
Copyright 2006-2010 Steffen Mueller.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.