It's a Time for Tolerance
Secret Origins!
Once upon a time, I was called upon to write a database for complex semiconductor growth specifications. Very often, the specification would state that a given measurement had to be within a certain tolerance, rather than a specific value. For bizarre semi-political reasons, we couldn't just provide outer limits and shoot for a center point. We had to track, through all parts of the system, the specific way in which the specification was... specified. That might be like any of the following:
  5
  > 5
  ≥ 5
  < 5
  ≥ 5
  5 ± 1
  5 ± 20%
  4 .. 6
  5 (-1 to +2)At the time, the need to stick to a given format was frustrating, but it drove me to write a library that has proven itself useful again and again -- mostly in weird situations, but I'm still happy to have a tool I only use once in a while.
Tolerance in Form and Action
A tolerance is an object that acts like a number for the sake of comparison.
Rather than struggle to contrive a generic example, I'll talk about how this code was originally used. Our customer is going to come to us with a specification for a product, and we want to be able to record that specification, overlay our own specifications, and then test results against it. The customer might give us the following set of requirements:
| 1:  | use Number::Tolerant; | 
Internally, we know what our production equipment's capabilities are:
| 1:  | $spec{machine} = { | 
...and engineering doesn't want to use anything too heavy, and have done some final design work, so they tack on:
| 1:  | $spec{engineering} = { | 
Now we have a bunch of different, separate specification documents, and we can have a quick review of them all:
| 1:  | sub tol_string { | 
...to get...
  CUSTOMER
       depth 182 +/- 5%
         hue 630 <= x <= 740
      weight x < 10
       width 10 +/- 2
  ENGINEERING
      weight x < 2
       width 9
  MACHINE
       depth x < 184
         hue 675
      weight x < 50
       width 7 <= x <= 10Composing these together is easy:
| 1:  | my $final_spec = {}; | 
...and we get:
     depth 172.9 <= x < 184
       hue 675
    weight x < 2
     width 9What just happened? Well, tolerances can be joined together logically with intersections and unions. When we want to produce the intersection of a bunch of tolerances, we just use the & operator. Or, if you want to avoid weird overloading, you can use the union method. When unions intersect, the result is either a new tolerance (like the unified depth specification above) or a constant giving the only permissible value. When a union would produce an impossible tolerance, an exception is thrown. I accidentally caused one of those when writing the above code. I'd picked a bad hue for the machine spec and got:
  No valid intersection of (630 <= x <= 740) and (775) at...Once we've gotten our final product, it's easy to test against the spec:
| 1:  | my $final_product = get_final_product; | 
...which might produce an error like:
  weight (2.2) outside of specification (x < 2)Testing with Tolerance
Tolerances can also be handy for running automated tests. An output file's size should fall into a given range, or we should process no more than a certain number of records, and so on. Using Number::Tolerant with tests can be useful when tolerances must be combined -- but it's often just easier to use than Test::More's cmp_ok, which has pretty gross semantics. For example, compare:
| 1:  | use Test::More; | 
to:
| 1:  | use Test::Tolerant; | 
Test::Tolerant is part of the Number-Tolerant distribution.