Michael Conrad


Math::InterpolationCompiler - Compile interpolations into perl coderefs


version 0.002000


  my $fn= Math::InterpolationCompiler->new(
    domain        => [ 1,   2,   3,   4,   5    ],
    range         => [ 1.9, 1.3, 1.2, 1.1, 1.05 ],
    algorithm     => 'linear',
    beyond_domain => 'die',
  print $fn->(3);   # 1.2
  print $fn->(3.5); # 1.15
  print $fn->(9);   # throws exception
  # The generated code:  (whitespace added for readability)
  # sub {
  #   my $x= shift;
  #   return $x < 4?
  #     ( $x < 2?
  #        ( $x < 1? (croak "argument out of bounds (<1)") : ($x * -0.6 + 2.5) )
  #       :( $x < 3? ($x * -0.1 + 1.5) : ($x * -0.0999999999999999 + 1.5) )
  #     )
  #     :( $x < 5?
  #        ( $x * -0.05 + 1.3)
  #       :( $x == 5? (1.05) : croak "argument out of bounds (>5)" )
  #     );
  # }


This module is much the same theme as Math::Interpolate and Math::Interpolator::Linear but it compiles the interpolations into actual Perl code, and pre-calculates all the numbers so that the end result is Log2(N) comparisons and a multiply and an add, all done at the interpreter level. This makes it very fast for repeated calls.

Use this module if you have a few smallish data plots which you want to evaluate very quickly over and over again.

DO NOT use this module if you have an extremely large data array that changes frequently, if your data points are not plain scalars, or if you are extremely worried about code-injection attacks. (this module sanitizes the numbers you give it, but it is still generating perl code and in security-critical environments with untrusted input your best bet is to just avoid all string evals).

This generator is written as a Perl object which produces a coderef. This makes the generator easy to extend and re-use pieces for various goals. However the OO design is somewhat unnecessary, and really just a convenient implementation thanks to Moo. In other words, the object doesn't have much use other than performing a one-time job.



The input values ('x') of the function. Domain must be sorted in non-decreasing order. Repeated values can represent discontinuities in the line.


The output values ('y') of the function.


The name of the algorithm to create:


Create a linear interpolation, where an input ('x') is matched to the domain interval containing that value, and the return value is

  y = x * (y_next - y_prev) / (x_next - x_prev)

If a domain 'x' coordinate is specified more than once (describing infinite slope) it is treated as a discontinuity. The 'x' values less than the discontinuity are interpolated using the segment before the discontinuity, and the 'x' values equal or greater than the discontinuity use the segment after the discontinuity.


  # domain => [ 1, 2, 2, 3 ],
  # range  => [ 0, 0, 1, 2 ],
  $fn->(1);   # equals 0
  $fn->(1.9); # equals 0
  $fn->(2);   # equals 1
  #fn->(3);   # equals 2

When beyond_domain is 'extrapolate' and a discontinuity occurs at the edge of the domain, the slope of the imaginary line outside of the domain is considered to be 0 (causing an effect like beyond_domain => 'clamp').


  # points => [[0,0], [0,1]]
  # beyond_domain => 'extrapolate'
  $fn->(x) # equals 0 for x < 0 and 1 for x >= 0


The behavior of the generated function when an input value ('x') lies outside the domain of the function.


Constrain 'x' to the valid domain of the function.


Extrapolate the curve at the nearest edge of the domain. Details of the extrapolation depend on the 'algorithm' being used.


Return undef for any 'x' outside the domain of the function.


Die with an error for any 'x' outside of the domain of the function.


Lazy-build the perl code for this function using the other attributes. Returns a string of perl code.


Lazy-build the perl coderef for the perl_code attribute.


Boolean. Whether or not to sanitize the domain and range with a 'number' regex during the constructor. Defaults to true.

Setting this to false leaves you open to code injection attacks, but you might choose to do that if you trust your input and you need a little performance boost on constructing this object.



Standard object constructor accepting any of the above attributes, but also accepting:

  ->new( points => [ [1,1], [2,2], [3,2], ... ] );
  # or
  ->new( points => [ 1 => 1, 2 => 2, 3 => 2, ... ] );

For convenience, you can specify your domain and range as an arrayref of (x,y) pairs. During BUILDARGS, this will get separated into the domain and range attributes. The pairs can either be individual arrayrefs, or just odd/even elements of a single arrayref (but not a mix of the two).


This module contains a few exportable functions for convenience.


  $fn= linear_clamp_fn( \@points )
  # equivalent to:
  $fn= Math::InterpolationCompiler->new(
      algorithm => 'linear',
      beyond_domain => 'clamp',
      points => \@points


  $fn= linear_clamp_fn( \@points )
  # equivalent to:
  $fn= Math::InterpolationCompiler->new(
      algorithm => 'linear',
      beyond_domain => 'extrapolate',
      points => \@points


Michael Conrad <mike@nrdvana.net>


This software is copyright (c) 2015 by Michael Conrad.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.