## This file generated by InlineX::C2XS (version 0.22) using Inline::C (version 0.5002)
package Math::Decimal64;

use 5.006;

use warnings;
use strict;

require Exporter;
*import = \&Exporter::import;
require DynaLoader;

our $VERSION = '0.03';
$VERSION = eval $VERSION;

use subs qw(DEC64_MAX DEC64_MIN);

DynaLoader::bootstrap Math::Decimal64 $Math::Decimal64::VERSION;

@Math::Decimal64::EXPORT = ();
@Math::Decimal64::EXPORT_OK = qw(
    MEtoD64 UVtoD64 IVtoD64 NVtoD64 PVtoD64 STRtoD64 D64toME D64toNV 
    InfD64 NaND64 UnityD64 ZeroD64 is_InfD64 is_NaND64 is_ZeroD64
    D64toLD LDtoD64 DEC64_MAX DEC64_MIN   
    assignME Exp10 have_strtod64
    );

%Math::Decimal64::EXPORT_TAGS = (all => [qw(
    MEtoD64 UVtoD64 IVtoD64 NVtoD64 PVtoD64 STRtoD64 D64toME D64toNV 
    InfD64 NaND64 UnityD64 ZeroD64 is_InfD64 is_NaND64 is_ZeroD64
    D64toLD LDtoD64 DEC64_MAX DEC64_MIN  
    assignME Exp10 have_strtod64
    )]);

use overload
  '+'     => \&_overload_add,
  '*'     => \&_overload_mul,
  '-'     => \&_overload_sub,
  '/'     => \&_overload_div,
  '+='    => \&_overload_add_eq,
  '*='    => \&_overload_mul_eq,
  '-='    => \&_overload_sub_eq,
  '/='    => \&_overload_div_eq,
  '""'    => \&_overload_string,
  '=='    => \&_overload_equiv,
  '!='    => \&_overload_not_equiv,
  '<'     => \&_overload_lt,
  '>'     => \&_overload_gt,
  '<='    => \&_overload_lte,
  '>='    => \&_overload_gte,
  '<=>'   => \&_overload_spaceship,
  '='     => \&_overload_copy,
  '!'     => \&_overload_not,
  'bool'  => \&_overload_true,
  'abs'   => \&_overload_abs,
  '++'    => \&_overload_inc,
  '--'    => \&_overload_dec,
  'int'   => \&_overload_int,
;

sub dl_load_flags {0} # Prevent DynaLoader from complaining and croaking

sub _overload_string {
    my @ret = D64toME($_[0]);
    if(is_InfD64($_[0]) || !$_[0]) {return $ret[0]}
    return $ret[0] . 'e' . $ret[1];
}

sub _overload_int {
    if(is_NaND64($_[0]) || is_InfD64($_[0]) || is_ZeroD64($_[0])) {return $_[0]}
    my($man, $exp) = D64toME($_[0]);
    if($exp >= 0) {return $_[0]}
    my $man_length = length($man);
    $man_length-- if $man =~ /^\-/;
    if(-$exp >= $man_length) {              # -1 <= $_[0] <= 1
       my $z = ZeroD64(1);
       if($_[0] < $z) {return ZeroD64(-1)}  # return -0
       return $z;                           # return  0
    }

    substr($man, $exp, -$exp, '');
    return MEtoD64($man, 0);
}

sub new {

    # This function caters for 2 possibilities:
    # 1) that 'new' has been called OOP style - in which 
    #    case there will be a maximum of 2 args
    # 2) that 'new' has been called as a function - in
    #    which case there will be a maximum of 1 arg.
    # If there are no args, then we just want to return a
    # Math::Decimal64 object that's a NaN.
    
    if(!@_) {return NaND64(1)}
   
    if(@_ > 3) {die "More than 3 arguments supplied to new()"}

    # If 'new' has been called OOP style, the first arg is the string
    # "Math::Decimal64" which we don't need - so let's remove it. However,
    # if the first arg is a Math::Decimal64 object (which is a possibility),
    # then we'll get a fatal error when we check it for equivalence to
    # the string "Math::Decimal64". So we first need to check that it's not
    # an object - which we'll do by using the ref() function:
    if(!ref($_[0]) && $_[0] eq "Math::Decimal64") {
      shift;
      if(!@_) {return NaND64(1)}
      } 

    # @_ can now contain max of 2 vals - the mantissa and exponent.
    # If @_ == 1 then it contains the value. 
    if(@_ > 2) {die "Too many arguments supplied to new() - expected no more than 2"}

    if(@_ == 2) {return MEtoD64(shift, shift || 0)} # In case second arg is undef

    my $arg = shift;
    my $type = _itsa($arg);

    if($type == 1) { # UV
      return UVtoD64($arg);
    }

    if($type == 2) { # IV
      return IVtoD64($arg);
    }

    if($type == 3) { # NV
      return NVtoD64($arg);
    }
    
    if($type == 4) { # PV
      return STRtoD64($arg) if have_strtod64();
      return PVtoD64($arg);
    }

    if($type == 64) { # Math::Decimal64 object
      return D64toD64($arg);
    }

    die "Bad argument given to new";
}

sub D64toME {
    return ('-0', '0') if (is_ZeroD64($_[0]) == -1); # Negative Zero.
    my @ret = _D64toME($_[0]);
    if(!defined($ret[1])) {
      @ret = _sci2me($ret[0]);
    }
    return @ret;
}

sub MEtoD64 {
  # Check that 2 args are supplied
  die "MEtoD64 takes 2 args" if @_ != 2;

  my $arg1 = shift;
  my $arg2 = shift;

  die "Invalid 1st arg ($arg1) to MEtoD64" if $arg1 =~ /[^0-9\-]/;
  die "Invalid 2nd arg ($arg2) to MEtoD64" if $arg2 =~ /[^0-9\-]/;

  my $len_1 = length($arg1);
  $len_1-- if $arg1 =~ /^\-/;

  if($len_1 > 16) {
    die "$arg1 exceeds _Decimal64 precision.",
        " It needs to be shortened to no more than 16 decimal digits";
  }

  return _MEtoD64($arg1, $arg2);

}

sub _sci2me {
    my @ret = split /e/i, $_[0];
    chop $ret[0] while $ret[0] =~ /0\b/;
    my @adj = split /\./, $ret[0];
    my $adj = defined $adj[1] ? length($adj[1])
                              : 0;
    $ret[0] =~ s/\.//;
    $ret[1] -= $adj;
    return @ret; 
}

sub DEC64_MAX {return _DEC64_MAX()}
sub DEC64_MIN {return _DEC64_MIN()}

1;

__END__

=head1 NAME

Math::Decimal64 - (alpha) perl interface to C's _Decimal64 operations.

=head1 DEPENDENCIES

   In order to compile this module, a C compiler that provides
   the _Decimal64 type is needed.

=head1 DESCRIPTION

   Note that this module is alpha software. It seems to work ok
   for me on MS Windows (compiling with gcc-4.6.3, gcc-4.7.0)
   but, at time of release, is untested elsewhere.

   Math::Decimal64 supports up to 16 decimal digits of significand
   (mantissa) and an exponent range of -383 to +384.
   The smallest expressable value is -9.999999999999999e384 (which
   is also equivalent to -9999999999999999e369).
   The largest expressable value is 9.999999999999999e384 (which
   also equivalent to 9999999999999999e369).
   The closest we can get to zero is (plus or minus) 1e-384
   (which is also equivalent to 1000000000000000e-399).

   This module allows decimal floating point arithmetic via
   operator overloading - see "OVERLOADING".

   In the documentation that follows, "$mantissa" is a perl scalar
   holding a string of up to 16 decimal digits:
    $mantissa = '1234';
    $mantissa = '1234567890123456';

   For many values, it normally shouldn't matter if $mantissa is 
   assigned as a number:
    $mantissa = 1234;      # should work ok.

   But on some perls there are values that *need* to be assigned
   as a string. For example, on perls where nvtype is an 8 byte
   'double':
    $mantissa = '-9307199254740993'; # works fine
    $mantissa = -9307199254740993;   # will assign wrong value

   So ... where you see "$mantissa" in the following docs, think
   *string* of up to 16 decimal digits".

=head1 SYNOPSIS

   use Math::Decimal64 qw(:all);

   my $d64_1 = MEtoD64('9927', -2); # the decimal 99.27
   my $d64_2 = MEtoD64('3', 0);     # the decimal 3.0
   $d64_1 /= $d64_2;
   print $d64_1; # prints 3309e-2 (33.09)

=head1 OVERLOADING

   The following operations are overloaded:
    + - * /
    += -= *= /=
    != == <= >= <=> < >
    ++ --
    =
    abs bool int print

    Arguments to the overloaded operations must be Math::Decimal64
    objects.

     $d64_2 = $d64_1 + 3.1; # currently an error. Do either:

     $d64_2 = $d64_1 + MEtoD64('31',-1); # or:
     $d64_2 = $d64_1 + Math::Decimal64->new('3',0);

=head1 ASSIGNMENT FUNCTIONS

    The following create and assign a new Math::Decimal64 object.
    Afaik, MEtoD64 and STRtoD64 will assign the values accurately;
    the other assignment functions can be subject to rounding 
    errors under certain conditions - use them with caution.

     ###################################
     # Assign from mantissa and exponent
     $d64 = MEtoD64($mantissa, $exponent);
       
      eg: $d64 = MEtoD64('12345', -3); # 12.345
    
      Doing Math::Decimal64->new($mantissa, $exponent) will also
      create and assign using MEtoD64().

     #####################################
     # Assign from a UV (unsigned integer)
     $d64 = UVtoD64($uv);

      eg: $d64 = UVtoD64(~0);

      Doing Math::Decimal64->new($uv) will also create and assign
      using UVtoD64().

     ####################################
     # Assign from an IV (signed integer)
     $d64 = IVtoD64($iv);

      eg: $d64 = IVtoD64(-15); # -15.0
      
      Doing Math::Decimal64->new($iv) will also create and assign
      using IVtoD64().

     ###########################
     # Assign from an NV (real))
     $d64 = NVtoD64($nv);

      eg: $d64 = NVtoD64(-3.25);

      Doing Math::Decimal64->new($nv) will also create and assign
      using NVtoD64().

     ######################
     # Assign from a string
     $d64 = PVtoD64($string);

      eg: $d64 = PVtoD64('-9427199254740993');
          $d64 = PVtoD64('-9307199254740993e-15');
          $d64 = Math::Decimal64->new('-9787199254740993');
          $d64 = Math::Decimal64->new('-9307199254740993e-23');

      Doing Math::Decimal64->new($string) will also create and
      assign using PVtoD64().

     ################################################
     # Assign from an existing Math::Decimal64 object
     $d64 = D64toD64($d64_0);
     Also:
      $d64 = Math::Decimal64->new($d64_0);
      $d64 = $d64_0; # uses overloaded '='

     ####################
     # Assign using new()
     $d64 = Math::Decimal64->new([$arg1, [$arg2]]);
      This function calls one of the above functions. It
      determines the appropriate function to call by examining
      the argument(s) provided.
      If no argument is provided, a Math::Decimal64 object
      with a value of NaN is returned.
      If 2 arguments are supplied it uses MEtoD64().
      If one argument is provided, that arg's flags are used
      to determine the appropriate function to call.

     #######################
     # Assign using STRtoD64
     $d64 = STRtoD64($string);
      If your C compiler provides the strtod64 function, and
      you configured the Makefile.PL to enable access to that
      function then you can use this function. 
      usage is is as for PVtoD64().

     ##############################
     # Assign to an existing object
     assignME($d64, $mantissa, $exponent);
      Assigns the value represented by ($mantissa, $exponent)
      to the Math::Decimal64 object, $d64.

     eg: assignME($d64, '123459', -6); # 0.123459

=head1 ASSIGNMENT OF INF, NAN and ZERO

     $d64 = InfD64($sign);
      If $sign < 0, returns a Math::Decimal64 object set to
      negative infinity; else returns a Math::Decimal64 object set
      to positive infinity.

     $d64 = NaND64($sign);
      If $sign < 0, returns a Math::Decimal64 object set to
      negative NaN; else returns a Math::Decimal64 object set to
      positive NaN. It may be problematical as to whether a NaN
      with the correct sign has been returned ... but, either way,
      it should return a NaN.

     $d64 = ZeroD64($sign);
      If $sign < 0, returns a Math::Decimal64 object set to
      negative zero; else returns a Math::Decimal64 object set to 
      zero.

=head1 RETRIEVAL FUNCTIONS

    The following functions provide ways of seeing the value of
    Math::Decimal64 objects.

     $nv = D64toNV($d64);
      This function returns the value of the Math::Decimal64
      object to a perl scalar (NV). Under certain conditions
      it may not translate the value accurately.

     ($mantissa, $exponent) = D64toME($d64);
      Returns the value of the Math::Decimal object as a 
      mantissa (string of up to 16 decimal digits) and exponent.
      You can then manipulate those variables to output the
      value in your preferred format. Afaik, the value will be
      translated accurately.

     print $d64;
      Will print the value in the format (eg) -12345e-2, which
      equates to the decimal -123.45. Afaik, the value will be
      translated accurately.

=head1 OTHER FUNCTIONS
     $d64 = DEC64_MAX; # 9999999999999999e369
     $d64 = DEC64_MIN; # 1e-398
      DEC64_MAX is the largest positive finite representable
      _Decimal64 value.
      DEC64_MIN is the smallest positive non-zero representable
      _Decimal64 value.
      Multiply these by UnityD64(-1) to get their negative
      counterparts.

     $d64 = Exp10($pow);
      Returns a Math::Decimal64 object with a value of
      10 ** $pow, for $pow in the range (-398 .. 384). Croaks
      with appropriate message if $pow is not within that range.

     $bool = have_strtod64();
      Returns true if, when building Math::Decimal64,
      the Makefile.PL was configured to make the STRtoD64()
      function available for your build of Math::Decimal64. Else
      returns false.
      (No use making this functionc available if your compiler's
      C library doesn't provide the strtod64 function.)
      

     $test = is_ZeroD64($d64);
      Returns:
       -1 if $d64 is negative zero;
        1 if $d64 is a positive zero;
        0 if $d64 is not zero.

     $test = is_InfD64($d64);
      Returns:
       -1 if $d64 is negative infinity;
        1 if $d64 is positive infinity;
        0 if $d64 is not infinity.

     $bool = is_NaND64($d64);
      Returns:
        1 if $d64 is a NaN;
        0 if $d64 is not a NaN.


=head1 LICENSE

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

=head1 AUTHOR

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

=cut

    