
=pod

=head1 NAME

  Math::FakeDD - DoubleDouble precision arithmetic for all architectures

=head1 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:
  L<https://en.wikipedia.org/wiki/Quadruple-precision_floating-point_format>

=head1 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.

=head1 SYNOPSIS

  use Math::FakeDD qw(:all);
  my $obj = Math::FakeDD->new('0.1');
  print $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);
  print $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.
  print sprintx($obj);  # Same as 'printx($obj)'.
  print dd_repro($obj); # $obj in scientific notation (in decimal).

  # Verify that the string returned by dd_repro($obj) is correct:
  print "ok" if dd_repro_test(dd_repro($obj), $obj) == 15;

  print 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.

=head1 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.
   Strings begining with '(+/-) '0b' will be evaluated as base 2 values.


=head1 ARITHMETIC FUNCTIONS

  Use either the functions, or the overloaded
  operations (that use these functions).

  $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.

=head1 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.

=head1 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...) .

=head1 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).

=head1 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;

=head1 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.
   If $Math::FakeDD::REPRO_PREC begins with ">", then the value that
   dd_repro() returned was altered slightly (away from zero) from
   the value that was calculated using the specified precision.
   If $Math::FakeDD::REPRO_PREC begins with "<", then the value that
   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);

   Return a string that begins with "[" and ends with "]".
   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:
     print(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 print($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.

=head1 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.

=head1 OTHER FUNCTIONS

  ($nv, $iv) = dd_frexp($nv_arg);

   For the NV $nv_arg, calculate $nv and $iv as per the C standard
   library's frexp() function. $nv_arg is cast to a double before
   being passed to frexp(). The returned $nv has therefore also
   been calculated to double precision.
   This function is provided mainly for internal use (in dd_repro)
   so that we can easily determine whether $obj->{lsd} or
   $obj->{msd} is an integer power of 2.

  $obj = dd_inf([$iv]); # $iv is an optional arg.

   If the optional arg $iv, is less than 0, then a Math::FakeDD
   object with value -Inf will be 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
   object, use dd2mpfr() instead.

  $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.


=head1 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.

=head1 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.

=head1 LICENSE

  This program is free software; you may redistribute it and/or
  modify it under the same terms as Perl itself.
  Copyright 2022-26, Sisyphus

=head1 AUTHOR

  Sisyphus <sisyphus at(@) cpan dot (.) org>

=cut
