NAME
Math::FakeDD - DoubleDouble precision arithmetic
for
all architectures
DEPENDENCIES
This module needs Math::MPFR, which in turn requires the GMP and MPFR
C libraries.
The GMP library is available from : L<https://gmplib.org>
The MPFR library is available from : L<https://www.mpfr.org>
See the introductory explanation of
"DoubleDouble"
arithmetic in the
"Double-double arithmetic"
section of:
DESCRIPTION
Perform DoubleDouble arithmetic operations on all architectures and
perl configurations.
See ARITMETIC IMPLEMENTATION section below.
As of version 1.01, the internals of this module have changed, and many
of the functions have been rewritten.
However, the user interface remains unchanged.
I'm hoping that, over
time
, these changes will help me to reduce the
insaneness of the path that dd_repro() takes.
In earlier versions, the Math::FakeDD objects carried
with
them only
the more significant double (
$obj
->{msd}) and the less significant
double (
$obj
->{lsd}).
They now also hold the 2098 bit value (
$obj
->{mpfr}), in the form of
a Math::MPFR object which holds an exact 2098-bit representation of
the value stored by the pair of doubles.
SYNOPSIS
my
$obj
= Math::FakeDD->new(
'0.1'
);
$obj
;
# Outputs [0.1 -5.551115123125783e-18] irrespective of
# perl's NV type. We see that the more significant double
# is 0.1, identical to the double precision representation
# of 0.1, and the less signficant double is
# -5.551115123125783e-18.
my
$obj
= Math::FakeDD->new(0.1);
$obj
;
# Outputs [0.1 0.0] if NV type is 'double'.
# Outputs [0.1 -5.549759870410176e-18] if NV type is
# extended precision 80-bit long double.
# Otherwise outputs [0.1 -5.551115123125783e-18], same
# as for string arg of '0.1'.
Generally, providing a string argument will DWYW, whereas providing a
floating-point (NV) argument won't. (Of course, exceptions exist and
you should familiarise yourself
with
the difference
if
it is not
already clear to you.)
printx(
$obj
);
# Same as for print() except that values are in hex.
sprintx(
$obj
);
# Same as 'printx($obj)'.
dd_repro(
$obj
);
# $obj in scientific notation (in decimal).
# Verify that the string returned by dd_repro($obj) is correct:
"ok"
if
dd_repro_test(dd_repro(
$obj
),
$obj
) == 15;
dd_dec(
$obj
);
# Exact representation of the value held by $obj,
# in scientific notation .
NOTES: In the following documentation:
"$arg"
,
"$arg1"
,
"$arg2"
, etc. are arguments that can be either a
string (perl PV), a native perl numeric type (NV or IV or UV), or
a Math::FakeDD object.
"$obj"
is a Math::FakeDD object.
"$mpfr"
is a 2098-bit precision Math::MPFR object.
ASSIGNMENT FUNCTIONS
$obj
= Math::FakeDD->new(
$arg
);
# $arg is optional here.
$obj
= Math::FakeDD::new(
$arg
);
# $arg is optional here.
Returns a Math::FakeDD object
with
the value represented by
$arg
,
or zero
if
no
argument is provided.
$arg
must be one of PV (string), IV (integer), UV (unsigned integer),
or NV (perl floating point type).
A warning that Rmpfr_set_str contains non-numeric characters will be
issued
when
assigning a PV that contains non-numeric characters.
dd_assign(
$obj
,
$arg
);
Assigns the value represented by
$arg
, to the Math::FakeDD object.
$arg
must be one of PV (string), IV (integer), UV (unsigned integer),
NV (perl floating point type) or Math::MPFR object.
$obj2
= dd_clone(
$obj1
);
$obj2
= dd_copy (
$obj1
);
Both dd_clone() and dd_copy()
return
a copy of the Math::FakeDD
object that was
given
to them. dd_clone is an alias
for
dd_copy.
$obj
= any2dd(
@args
);
Converts all of the arguments to Math::FakeDD objects and returns
the sum of all of those objects.
Each argument must be one of PV (string), IV (integer), UV (unsigned
integer), NV (perl floating point type) or Math::MPFR object, but
they don't all have to be of the same type.
Strings beginning
with
(+/-)
'0x'
or
'0X'
will be evaluated as
hex
values
. This is handy
for
re-creating
values
that have been displayed
using (s)printx.
ARITHMETIC FUNCTIONS
Use either the functions, or the overloaded
$obj
= dd_add(
$arg1
,
$arg2
);
$obj
= dd_sub(
$arg1
,
$arg2
);
$obj
= dd_mul(
$arg1
,
$arg2
);
$obj
= dd_div(
$arg1
,
$arg2
);
Returns a Math::FakeDD object
with
the value of
$arg1
(respectively) plus, minus, multiplied by or divided by
$arg2
.
dd_add_eq(
$obj
,
$arg
);
dd_sub_eq(
$obj
,
$arg
);
dd_mul_eq(
$obj
,
$arg
);
dd_div_eq(
$obj
,
$arg
);
The value in
$obj
is (respectively) incremented, decremented,
multiplied or divided by
$arg
.
$obj
= dd_pow(
$arg1
,
$arg2
);
Return a Math::FakeDD object that contains
$arg1
**
$arg2
.
dd_pow_eq(
$obj
,
$arg
);
The value held in
$obj
is raised to the power of
$arg
.
(That is,
$obj
=
$obj
**
$arg
.)
$obj
= dd_abs(
$arg
);
Return a Math::FakeDD object containing the absolute value of
$arg
.
$obj
= dd_int(
$arg
);
Return a Math::FakeDD object containing the truncated
(integer) value of
$arg
.
EXPONENTIATION FUNCTIONS
$obj
= dd_exp(
$arg
);
$obj
= dd_exp2(
$arg
);
$obj
= dd_exp10(
$arg
);
Return a Math::FakeDD object that contains respectively
e **
$arg
, 2 **
$arg
, 10 **
$arg
.
$obj
= dd_log(
$arg
);
$obj
= dd_log2(
$arg
);
$obj
= dd_log10(
$arg
);
Return a Math::FakeDD object that contains respectively
the natural
log
of
$arg
, the base 2
log
of
$arg
, the
base 10
log
of
$arg
.
TRANSCENDENTAL FUNCTIONS
$obj
= dd_pi();
$obj
= dd_euler();
$obj
= dd_catalan();
Return (respectively) pi (3.141...), Euler's constant (0.577...)
and Catalan's constant (0.915...) .
TRIGONOMETRY FUNCTIONS
$obj
= dd_cos(
$arg
);
Return a Math::FakeDD object that contains
cos
(
$arg
).
$obj
= dd_sin(
$arg
);
Return a Math::FakeDD object that contains
sin
(
$arg
).
COMPARISON FUNCTIONS
$iv
= dd_cmp(
$arg1
,
$arg2
);
$iv
= dd_spaceship(
$arg1
,
$arg2
);
Return
undef
if
at least one of
$arg1
and
$arg2
is NaN
$iv
< 0
if
$arg1
<
$arg2
.
$iv
> 0
if
$arg1
>
$arg2
.
$iv
== 0
if
$arg1
==
$arg2
.
$iv
= dd_eq (
$arg1
,
$arg2
);
$iv
= dd_neq(
$arg1
,
$arg2
);
$iv
= dd_gt(
$arg1
,
$arg2
);
$iv
= dd_gte(
$arg1
,
$arg2
);
$iv
= dd_lt(
$arg1
,
$arg2
);
$iv
= dd_lte(
$arg1
,
$arg2
);
If (respectively)
$arg1
==
$arg2
,
$arg1
!=
$arg2
,
$arg1
>
$arg2
,
$arg1
>=
$arg2
,
$arg1
<
$arg2
,
$arg1
<=
$arg2
,
then
$iv
is set to 1.
Else
$iv
is set to 0.
$iv
= dd_streq(
$obj
,
$arg
);
We need this function only because Test::More can pull
in code that assumes
'eq'
is overloaded. (And this is the
function that overloaded
'eq'
calls.)
Return 1
if
"$obj"
eq
"$arg"
.
Else
return
0;
$iv
= dd_strne(
$obj
,
$arg
);
We provide this function only because dd_stre() is provided.
Return 1
if
"$obj"
ne
"$arg"
.
Else
return
0;
OUTPUT FUNCTIONS
$str
= dd_dec(
$obj
);
# "dec" short for "decimalize"
Represent the
*exact
* value of
$obj
in scientific notation,
using as few digits as required to provide that exactness.
As
with
dd_repro(),
if
this
$str
is assigned to a new
Math::FakeDD object, then the new Math::FakeDD object will
be identical to the Math::FakeDD object from which this
$str
was derived. However, the number of mantissa digits
returned by dd_dec() will be >= (usually greater than) the
number of mantissa digits returned by dd_repro().
$str
= dd_hex(
$obj
);
As
for
dd_dec(), except that the value placed in
$str
is in
hex
(
"%a"
)
format
.
$str
= dd_repro(
$obj
);
# "repro" short for "reproducible"
#### NOTE: This function has turned into a much uglier hack
than I had envisaged. See the
"TODO"
section below
for
elaboration.
Represent the value of
$obj
in decimal scientific notation,
using as few decimal mantissa digits as possible, such that
if
this
$str
is assigned to a new Math::FakeDD object, then the
new Math::FakeDD object will be identical to (ie a reproduction
of) the Math::FakeDD object from which
$str
was derived.
Also set
$Math::FakeDD::REPRO_PREC
to the bit-precision value
that was used in determining
$str
.
dd_repro() returned was altered slightly (away from zero) from
the value that was calculated using the specified precision.
dd_repro() returned was altered slightly (toward zero) from the
value that was calculated using the specified precision.
Other possible
$Math::FakeDD::REPRO_PREC
values
, all of which
indicate that
no
calcuation of precision
has
been made, are:
-1: the initial value, and dd_repro()
has
not been called at all;
0 : dd_repro returned an Inf, NaN or Zero;
undef
: perl's nvtype is DoubleDouble, so dd_repro() simply used
Math::MPFR::nvtoa().
$iv
= dd_repro_test(dd_repro(
$obj
),
$obj
[,
$opt1
[,
$opt2
]] );
Test that dd_repro(
$obj
) returns the correct string. Be careful
that the string provided as 1st arg is as returned by dd_repro(
$obj
).
$opt1
and
$opt2
are optional, and will have
no
effect
if
they don't
match either /debug/i or /examine/i.
If
$obj
is either zero, or an inifinity or a NaN, then we check
that the string matches the expected form. Upon success,
return
15;
upon failure, 0 is returned. No other checks are done though,
if
/examine/i was matched, then
$Math::FakeDD::examine
{repro} will
be set to the string that was tested and the other
%Math::FakeDD::examine
keys
will be set to the empty string.
For non zero/inf/nan objects, we check that the representation
returned by dd_repro(
$obj
) is accurate, and that it uses the fewest
significant decimal mantissa digits possible.
Some debugging output is provided
if
an optional arg matches /debug/i.
If an arg instead matches /examine/i then the
%Math::FakeDD::examine
keys
will be populated
with
the
values
that this function
has
used
for
its testing (as indicated below) .
The function assigns its first arg, dd_repro(
$obj
), to a
string - let's call it
$str
.
The following 2 tests are conducted:
1) check that Math::FakeDD->new(
$str
) ==
$obj
.
Set
$iv
to 1
if
and only
if
this test passes.
$Math::FakeDD::examine
{repro} will be set to
$str
if
the third
arg matches /examine/i.
$str
will be displayed
if
the third arg matches /debug/i.
2) check that there are
no
trailing zeroes in the mantissa part of
$str
.
Set
$iv
+= 8
if
and only
if
this test passes.
If the mantissa part of
$str
consists of only 1 digit, then
$iv
is
incremented by 6, and returned. The following two tests (3 and 4)
are omitted because they are irrelevant:
3)
chop
(
$str
) and check that Math::FakeDD->new(
$str
) <
$obj
.
Set
$iv
+= 2
if
and only
if
this test passes.
$Math::FakeDD::examine
{
chop
} will be set to
$str
if
the 3rd
or 4th arg matches /examine/i.
$str
will be displayed
if
the 3rd or 4th arg matches /debug/i.
4) increment (the chopped)
$str
; eg
$str
would change from
'1992'
to 1993, or from
'1999'
to
'2000'
.
Then test that Math::FakeDD->new(
$str
) >
$obj
.
Set
$iv
+= 4
if
and only
if
this test passes.
$Math::FakeDD::examine
{inc} will be set to
$str
if
the 3rd
or 4th arg matches /examine/i.
$str
will be displayed
if
the 3rd or 4th arg matches /debug/i.
If
$iv
== 15 then we know that dd_repro(
$obj
)
has
functioned
correctly and as intended.
Else, dd_repro()
has
not functioned correctly and we can determine
which test(s) failed by examining the 4 lowest bits of
$iv
.
$str
= dd_stringify(
$obj
);
In between show, a space-delimited decimal string
representation of the more significant double followed by the
less significant double. Both doubles will be shown in as few
digits as possible,
while
not losing any information.
The overloading of interpolation uses this function.
The returned string is NOT suitable as an argument to new(), or
to dd_assign(), or to mpfr2098().
$mpfr
= dd_numify(
$obj
);
# Mainly for '0+' overloading.
Beginning in version 1.01, this function returns
$_
[0]->{mpfr},
which is identical to what was returned in the previous version
(0.08) of Math::FakeDD .
Prior to 0.08 it returned an NV that held the value of
$obj
(as accurately as the precision of the NV allowed.)
printx(
$obj
);
Same as doing:
(sprintx(
$obj
));
# see below for sprintx documentation.
$str
= sprintx(
$obj
);
Same as dd_stringify(
$obj
), except that the 2 doubles are
presented in
hex
(
"%a"
)
format
, instead of decimal
format
.
NOTE: If perl's nvtype is the 80-bit extended precision
long double then the
"%a"
formatting will be different
(albeit numerically equivalent) to that which is
presented on perls that have a different nvtype; eg:
$Math::FakeDD::DD_MAX
will be stringified to
[0xf.ffffffffffff8p+1020 0xf.ffffffffffff8p+966]
instead of
[0x1.fffffffffffffp+1023 0x1.fffffffffffffp+969]
$str
= unpackx(
$obj
);
Same as dd_stringify(
$obj
), except that the 2 doubles have
each
been unpacked into
hex
format
by doing:
unpack
"H*"
,
pack
"d>"
,
$double
;
$str
= dd_dump(
$obj
);
Returns a string containing the following information:
1) a decimal representation of
$obj
,same as
(
$obj
),
except that the
values
are shown in
"%.17g"
format
;
2) a
hex
representation of
$obj
, same as printx(
$obj
);
3) a base 2 representation of
$obj
->{mpfr}, same as
Math::MPFR::Rmpfr_dump(
$obj
->{mpfr}), except that
trailing zeroes in the mantissa have been removed.
OPERATOR OVERLOADING
The following operations are currently overloaded:
""
, 0+, ==, !=, *, *=, **, **=, +, +=, -, -=, /, /=, <, <=, <=>, >, >=,
++, --,
bool, !,
atan2
,
cos
,
sin
eq, ne
=
Run
my
%oloads
= Math::FakeDD::oload();
The
keys
of
%oloads
are the overloaded operations, and their
respective
values
name the functions used in the respective operation.
These functions are documented above - except
for
dd_true(),
dd_false() overload_inc, overload_dec,and overload_copy(), which
are documented here:
$iv
= dd_true(
$obj
);
This function is not exported.
It is intended to be called only by overloading of
'bool'
.
Returns 1
unless
$obj
is NaN or zero;
else
returns 0.
(This is the same criterion as used by Math::MPFR's overloading
of
'bool'
.)
$iv
= dd_false(
$obj
);
This function is not exported.
It is intended to be called only by overloading of
'!'
.
Returns 1
if
$obj
is NaN or zero;
else
returns 0.
(This is the same criterion as used by Math::MPFR's overloading
of
'!'
.)
$obj2
=
$obj1
;
# Utilizes overload_copy()
Use the unexported overload_copy()
sub
to copy
$obj1
to
$obj2
.
$obj1
++;
# Utilizes overload_inc()
$obj1
--;
# Utilizes overload_dec()
Add or (respectively) subtract 1e0 to/from
$obj1
.
OTHER FUNCTIONS
$obj
= dd_inf([
$iv
]);
# $iv is an optional arg, which will be
# evaluated in (signed) numeric context
If the optional arg
$iv
, is less than 0 (in numeric context, then
a Math::FakeDD object
with
value -Inf is returned.
Else
return
a +Inf Math::FakeDD object.
$obj
= dd_nan();
Return a NaN Math::FakeDD object.
$iv
= dd_is_inf(
$obj
);
Returns 1
if
$obj
is an infinity.
Else returns 0.
$iv
= dd_is_nan(
$obj
);
Returns 1
if
$obj
is a NaN.
Else returns 0.
$obj
= mpfr2dd(
$mpfr
);
# Precision of $mpfr is 2098 bits.
$obj
= mpfr_any_prec2dd(
$mpfr
);
# Precision of $mpfr is unrestricted.
Return the value held by the Math::MPFR object
$mpfr
as the
Math::FakeDD object
$obj
.
Precision can be (and often is) lost in this conversion
if
the
precision of
$mpfr
is greater than 107, or
if
$mpfr
contains a
value outside of the double precision range.
$mpfr
= dd2mpfr(
$obj
);
Return the value held by the Math::FakeDD object
$obj
as a 2098-bit
precision Math::MPFR object
$mpfr
.
No precision is lost in this conversion.
$mpfr
= mpfr2098(
$in
);
Return the value held by
$in
as the 2098-bit precision Math::MPFR
object
$mpfr
.
$in
can be a string (PV), an integer (IV), an unsigned integer (UV),
a perl floating point (NV) - but not a Math::FakeDD object.
To convert a Math::FakeDD object to a 2098-bit precision Math::MPFR
$obj2
= dd_nextup(
$obj1
);
$obj2
= dd_nextdown(
$obj1
);
Return the value that is greater than, or (respectively) less than,
the
given
argument by the smallest amount possible.
Return dd_nan()
if
$obj1
is NaN.
If
$obj1
is +Inf, dd_nextup() returns dd_inf(), dd_nextdown() returns
$Math::FakeDD::DD_MAX
.
If
$obj1
is -Inf, dd_nextup() returns -
$Math::FakeDD::DD_MAX
and
dd_nextdown() returns dd_inf(-1).
$iv
= ulp_exponent(
$obj
, [
$bool
]);
If
no
second argument,
return
the signed integer value (
$iv
) such that
2*
*$iv
equates to the value of ULP of the
given
argument's less
significant double.
If a second argument is
given
, and it evaluates to TRUE, then the value
of the ULP of the
given
argument's more sigfificant double is returned.
NOTE:
ULP is aka
"unit in last place"
or, synonymously, "unit of least
precision".
$iv
= is_subnormal(
$double
);
$double
is evaluated to double precision.
If that value is subnormal (
abs
(
$double
) < 2**-1022)
return
1.
Else 0 is returned.
NOTE:
This function returns 1
if
$double
is zero.
ARITHMETIC IMPLEMENTATION
Operands are first converted to a 2098-bit Math::MPFR object value.
If the operand is a Math::FakeDD object then the conversion is exact.
Else, the conversion might not be exact, depending upon the value.
The result of the operation on the 2098-bit operand(s) will also be
rounded to 2098 bits, and that value will then be rounded to its
DoubleDouble
format
and returned as a Math::FakeDD object.
DoubleDoubles
with
real
values
have precision of between 107 and
2098 bits (depending upon their value), except
for
subnormalized
double precision
values
where precision is in the range 1 to 52
bits (depending upon value).
All rounding is done to nearest,
with
ties to even.
TODO
Re-factor dd_repro(), which is currently just an awful hack.
Also, it
's possible that dd_repro() doesn'
t work correctly
for
all
possible DoubleDouble
values
. It's recommended that,
if
this
matters, then dd_repro_test() should be run to check on the
correctness of the
values
that dd_repro() returns.
Please report any such errors, as that will be helpful
with
the
intended re-factoring.
LICENSE
This program is free software; you may redistribute it and/or
modify it under the same terms as Perl itself.
Copyright 2022-23, Sisyphus
AUTHOR
Sisyphus <sisyphus at(@) cpan dot (.) org>