The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#define NEED_newRV_noinc_GLOBAL
#include "ppport.h"

/* double_ulong_max_plus_1 is ULONG_MAX+1.  This is usually a power of 2 and
   thus can be represented exactly in a double.  The calculation avoids
   round-off if ulong is bigger than the mantissa of a double.  */
static const double double_ulong_max_plus_1
  = ((double) ((ULONG_MAX >> 1)+1)) * 2.0;

#define COUNT_OR_PUSH(prime)                            \
  do {                                                  \
    if (ix) {                                           \
      count++;         /* count_prime_factors() */      \
    } else {                                            \
      mXPUSHu(prime);  /* prime_factors() */            \
    }                                                   \
  } while (0)

MODULE = Math::Factor::XS               PACKAGE = Math::Factor::XS

void
factors (number)
      unsigned long number
    PROTOTYPE: $
    INIT:
      unsigned long i, square_root;
      AV *factors;
    PPCODE:
      /* range check */
      {
        double d = SvNV(ST(0));
        if (! (d >= 0 && d < double_ulong_max_plus_1)) {
          croak ("Cannot factors() on %g", d);
        }
      }

      factors = newAV ();
      square_root = sqrt (number);

      for (i = 2; i <= number; i++)
        {
          if (i > square_root)
            break;
          if (number % i == 0)
            {
              unsigned long quot = number / i;
              mXPUSHu(i);
              if (quot > i)
                av_push (factors, newSVuv(quot));
            }
        }

      i = av_len(factors) + 1;
      EXTEND (SP, i);
      while (i--)
        PUSHs (sv_2mortal(av_pop(factors)));

      SvREFCNT_dec (factors);

void
xs_matches (number, factors_aref, ...)
      unsigned long number
      SV *factors_aref
    PROTOTYPE: $\@
    INIT:
      AV *factors;
      unsigned long *prev_base = NULL;
      unsigned int b, c, p = 0;
      unsigned int top = items - 1;
      bool Skip_multiples = FALSE;
      bool skip = FALSE;
    PPCODE:
      factors = (AV*)SvRV (factors_aref);

      if (av_len (factors) == -1)
        XSRETURN_EMPTY;

      if (SvROK (ST(top)) && SvTYPE (SvRV(ST(top))) == SVt_PVHV)
        {
          const char *opt = "skip_multiples";
          unsigned int len = strlen (opt);
          HV *opts = (HV*)SvRV (ST(top));

          if (hv_exists (opts, opt, len))
            {
              SV **val = hv_fetch (opts, opt, len, 0);
              if (val)
                Skip_multiples = SvTRUE (*val);
            }
        }

      for (b = 0; b <= av_len (factors); b++)
        {
          unsigned long base = SvUV (*av_fetch(factors, b, 0));
          for (c = 0; c <= av_len (factors); c++)
            {
              unsigned long cmp = SvUV (*av_fetch(factors, c, 0));
              if ((cmp >= base) && (base * cmp == number))
                {
                  if (Skip_multiples)
                    {
                      unsigned int i;
                      skip = FALSE;
                      for (i = 0; i < p; i++)
                        if (base % prev_base[i] == 0)
                          skip = TRUE;
                    }
                  if (!skip)
                    {
                      AV *match = newAV ();
                      av_push (match, newSVuv(base));
                      av_push (match, newSVuv(cmp));

                      EXTEND (SP, 1);
                      PUSHs (sv_2mortal(newRV_noinc((SV*)match)));

                      if (Skip_multiples)
                        {
                          if (!prev_base)
                            Newx (prev_base, 1, unsigned long);
                          else
                            Renew (prev_base, p + 1, unsigned long);
                          prev_base[p++] = base;
                        }
                    }
                }
            }
        }

      Safefree (prev_base);

# prime_factors() and count_prime_factors() done in a combined XSUB so
# as to use the share the factorizing loop and to save a few bytes of
# object code by sharing the boilerplate sub entry and exit.
#
# No PROTOTYPE since might add a "distinct" option.
#
void
prime_factors (number)
      unsigned long number
    ALIAS:
     count_prime_factors = 1
    INIT:
     unsigned long i, limit;
     unsigned incr;
     unsigned long count = 0;
    PPCODE:
      /* range check */
      {
        double d = SvNV(ST(0));
        if (! (d >= 0 && d < double_ulong_max_plus_1)) {
          croak ("Cannot prime_factors() on %g", d);
        }
      }

      if (number > 0) {
        /* or perhaps __builtin_ctz() in new enough gcc, but usually there
           won't be many twos */
        while (! (number & 1)) {
          COUNT_OR_PUSH(2);
          number >>= 1;
        }

        while (! (number % 3)) {
          COUNT_OR_PUSH(3);
          number /= 3;
        }

        /* "incr" is alternately 2 and 4, giving i==5mod6 and i==1mod6, so
           skip multiples of 2 and multiples of 3 */
        limit = sqrt (number);
        incr = 2;
        for (i = 5; i <= limit; i += incr, incr ^= 6)
          {
            if (number % i == 0)
              {
                do {
                  number /= i;
                  COUNT_OR_PUSH(i);
                } while (number % i == 0);
                limit = sqrt (number); /* new smaller limit */
              }
          }
      }

      if (ix) {
        /* count_prime_factors() */
        count += (number > 1);  /* possible prime left in number */
        mXPUSHu(count);
      } else {
        /* prime_factors() */
        if (number > 1) {
          mXPUSHu(number);  /* possible prime left in number */
        }
      }