File Coverage

blib/lib/Number/Interval.pm
Criterion Covered Total %
statement 240 266 90.2
branch 184 242 76.0
condition 101 135 74.8
subroutine 24 26 92.3
pod 17 17 100.0
total 566 686 82.5


line stmt bran cond sub pod time code
1             package Number::Interval;
2              
3             =head1 NAME
4              
5             Number::Interval - Implement a representation of a numeric interval
6              
7             =head1 SYNOPSIS
8              
9             use Number::Interval;
10              
11             $i = new Number::Interval( Min => -4, Max => 20);
12             $i = new Number::Interval( Min => 0 );
13              
14             $is = $i->contains( $value );
15             $status = $i->intersection( $i2 );
16              
17             print "$i";
18              
19             =head1 DESCRIPTION
20              
21             Simple class to implement a closed or open interval. Can be used to
22             compare different intervals, determine set membership, calculate
23             intersections and provide default stringification methods.
24              
25             Intervals can be bound or unbound. If C is less than C
26             the interval is inverted.
27              
28             =cut
29              
30 1     1   1141 use 5.006;
  1         2  
  1         37  
31 1     1   4 use strict;
  1         1  
  1         26  
32 1     1   14 use warnings;
  1         1  
  1         29  
33 1     1   4 use Carp;
  1         1  
  1         61  
34 1     1   530 use Data::Dumper;
  1         5399  
  1         93  
35             use overload
36 1         7 '""' => "stringify",
37             '==' => 'equate',
38             'eq' => "equate",
39             '!=' => "notequal",
40 1     1   5 'ne' => "notequal";
  1         1  
41              
42             # CVS ID: $Id$
43              
44 1     1   89 use vars qw/ $VERSION /;
  1         1  
  1         1854  
45             $VERSION = '0.07';
46              
47             # hash of allowed lower-cased constructor keys with
48             # corresponding accessor method
49             my %ConstructAllowed = (
50             min => 'min',
51             max => 'max',
52             incmax => 'inc_max',
53             incmin => 'inc_min',
54             posdef => 'pos_def',
55             );
56              
57             =head1 METHODS
58              
59             =head2 Constructor
60              
61             =over 4
62              
63             =item B
64              
65             Create a new object. Can be populated when supplied with
66             keys C and C.
67              
68             $r = new Number::Interval();
69              
70             This interval is Inf.
71              
72             $r = new Number::Interval( Max => 5 );
73              
74             This interval is > 5.
75              
76             $r = new Number::Interval( Max => 5, Min => 22 );
77              
78             This interval is > 22 and < 5.
79              
80             By default the interval does not include the bounds themselves. They
81             can be included by using the IncMax and IncMin keys.
82              
83             $r = new Number::Interval( Max => 5, IncMax => 1 );
84              
85             The above interval is >=5
86              
87             Positive-definite intervals allow the stringification to ignore
88             the lower bound if it is 0 (even if set explicitly).
89              
90             $r = new Number::Interval( Max => 5, IncMax => 1, Min => 0,
91             PosDef => 1);
92              
93             The keys are case-insensitive.
94              
95             =cut
96              
97             sub new {
98 117     117 1 2412 my $proto = shift;
99 117   66     384 my $class = ref($proto) || $proto;
100              
101 117         255 my %args = @_;
102              
103 117         312 my $r = {
104             Min => undef,
105             Max => undef,
106             IncMax => 0,
107             IncMin => 0,
108             PosDef => 0,
109             };
110              
111             # Create object
112 117         200 my $obj = bless $r, $class;
113              
114             # Populate it
115 117         352 for my $key (keys %args) {
116 193         191 my $lc = lc( $key );
117 193 50       360 if (exists $ConstructAllowed{$lc}) {
118 193         208 my $method = $ConstructAllowed{$lc};
119 193         355 $obj->$method( $args{$key} );
120             }
121             }
122              
123 117         361 return $obj;
124             }
125              
126             =item B
127              
128             Copy the contents of the current object into a new object and return it.
129              
130             $new = $r->copy;
131              
132             =cut
133              
134             sub copy {
135 5     5 1 93 my $self = shift;
136 5         7 my $new = $self->new();
137             # simplistic hash copy since we know that we are a simple hash internally
138             # subclasses might get in trouble if they have complexity.
139 5         18 %$new = %$self;
140 5         9 return $new;
141             }
142              
143             =back
144              
145             =head2 Accessors
146              
147             =over 4
148              
149             =item B
150              
151             Return (or set) the upper end of the interval.
152              
153             $max = $r->max;
154             $r->max(22.0);
155              
156             C indicates that the interval has no upper bound.
157              
158             =cut
159              
160             sub max {
161 549     549 1 466 my $self = shift;
162 549 100       983 $self->{Max} = shift if @_;
163 549         997 return $self->{Max};
164             }
165              
166             =item B
167              
168             Return (or set) the lower end of the interval.
169              
170             $min = $r->min;
171             $r->min( undef );
172              
173             C indicates that the interval has no lower bound.
174              
175             =cut
176              
177             sub min {
178 578     578 1 964 my $self = shift;
179 578 100       1043 $self->{Min} = shift if @_;
180 578         1030 return $self->{Min};
181             }
182              
183             =item B
184              
185             Return (or set) the boolean indicating whether the maximum bound
186             of the interval should be included in the bound definition. If true,
187             the bounds will be >= max.
188              
189             $inc = $r->inc_max;
190             $r->inc_max( 1 );
191              
192             Default is false (not included).
193              
194             =cut
195              
196             sub inc_max {
197 219     219 1 244 my $self = shift;
198 219 100       321 $self->{IncMax} = shift if @_;
199 219         437 return $self->{IncMax};
200             }
201              
202              
203             =item B
204              
205             Return (or set) the boolean indicating whether the minimum bound
206             of the interval should be included in the bound definition. If true,
207             the bounds will be <= min.
208              
209             $inc = $r->inc_min;
210             $r->inc_min( 1 );
211              
212             Default is false (not included).
213              
214             =cut
215              
216             sub inc_min {
217 205     205 1 142 my $self = shift;
218 205 100       318 $self->{IncMin} = shift if @_;
219 205         374 return $self->{IncMin};
220             }
221              
222             =item B
223              
224             Indicate that the interval is positive definite. This helps the
225             stringification method to determine whether the lower bound
226             should be included
227              
228             $r->pos_def( 1 );
229              
230              
231             If set to true, automatically sets the lower bound to 0 if the lower bound
232             is not explicitly defined.
233              
234             =cut
235              
236             sub pos_def {
237 28     28 1 307 my $self = shift;
238 28 100       45 if (@_) {
239 1         2 $self->{PosDef} = shift;
240 1 50 33     7 if ($self->{PosDef} && !defined $self->min) {
241 1         3 $self->min( 0 );
242             }
243             }
244 28         83 return $self->{PosDef};
245             }
246              
247             =item B
248              
249             Return (or set) the minimum and maximum values of the
250             interval as an array.
251              
252             $r->minmax( 1, 5 );
253             @interval = $r->minmax;
254              
255             Returns reference to an array in a scalar context.
256              
257             =cut
258              
259             sub minmax {
260 3     3 1 739 my $self = shift;
261 3 100       7 if (@_) {
262 2         6 $self->min( $_[0] );
263 2         4 $self->max( $_[1] );
264             }
265 3         16 my @minmax = ( $self->min, $self->max );
266 3 100       10 return (wantarray ? @minmax : \@minmax);
267             }
268              
269             =item B
270              
271             Return (or set) the minimum and maximum values of the
272             interval as an hash.
273              
274             $r->minmax_hash( min => 1, max => 5 );
275             %interval = $r->minmax_hash;
276              
277             Returns reference to an hash in a scalar context.
278              
279             C or C can be ommitted. The returned hash
280             contains C and C keys but only if they
281             have defined values.
282              
283             =cut
284              
285             sub minmax_hash {
286 0     0 1 0 my $self = shift;
287 0 0       0 if (@_) {
288 0         0 my %args = @_;
289 0 0       0 $self->min( $args{min} ) if exists $args{min};
290 0 0       0 $self->max( $args{max} ) if exists $args{max};
291             }
292              
293             # Populate the output hash
294 0         0 my %minmax;
295 0 0       0 $minmax{min} = $self->min if defined $self->min;
296 0 0       0 $minmax{max} = $self->max if defined $self->max;
297              
298 0 0       0 return (wantarray ? %minmax : \%minmax);
299             }
300              
301             =item B
302              
303             Returns the size of the interval.
304              
305             $sizeof = $r->sizeof;
306              
307             If either of the lower or upper ends are unbounded, then C will
308             be returned.
309              
310             =cut
311              
312             sub sizeof {
313 5     5 1 7 my $self = shift;
314 5 100 66     9 if( ! defined( $self->min ) ||
315             ! defined( $self->max ) ) {
316 3         10 return undef;
317             }
318              
319 2         3 return abs( $self->max - $self->min );
320             }
321              
322             =back
323              
324             =head2 General
325              
326             =over 4
327              
328             =item B
329              
330             Convert the object into a string representation for display.
331             Usually called via a stringify overload.
332              
333             =cut
334              
335             sub stringify {
336 9     9 1 48 my $self = shift;
337              
338 9         15 my $min = $self->min;
339 9         15 my $max = $self->max;
340              
341             # are we inclusive (for unbound ranges)
342 9 100       13 my $inc_min_ub = ( $self->inc_min ? "=" : " " );
343 9 100       13 my $inc_max_ub = ( $self->inc_max ? "=" : " " );
344              
345 9 50 33     32 if (defined $min && defined $max) {
    0          
    0          
346             # Bound
347              
348             # use standard interval notation when using a bound range
349 9 100       15 my $inc_min_b = ( $self->inc_min ? "[" : "(" );
350 9 100       34 my $inc_max_b = ( $self->inc_max ? "]" : ")" );
351              
352 9 100       23 if ($min == $max) {
    100          
353             # no range
354 4 100 100     6 if ($self->inc_min() or $self->inc_max()) {
355             # A single value.
356 3         15 return "==$min";
357             } else {
358             # This interval matches nothing.
359 1         5 return "{}";
360             }
361             } elsif ($max < $min) {
362 1         37 return "<$inc_max_ub$max and >$inc_min_ub$min";
363             } else {
364 4 100 66     15 if ($min <= 0 && $self->pos_def) {
365 2         11 return "<$inc_max_ub$max";
366             } else {
367 2         469 return "$inc_min_b$min,$max$inc_max_b";
368             }
369             }
370             } elsif (defined $min) {
371 0         0 return ">$inc_min_ub$min";
372             } elsif (defined $max) {
373 0         0 return "<$inc_max_ub$max";
374             } else {
375 0         0 return "Inf";
376             }
377              
378             }
379              
380             =item B
381              
382             Determine whether the interval is inverted. This is true if
383             both max and min are supplied but max is less than min. For all other
384             cases (including unbound single-sided intervals) this will return false.
385              
386             =cut
387              
388             sub isinverted {
389 136     136 1 100 my $self = shift;
390 136         143 my $min = $self->min;
391 136         174 my $max = $self->max;
392              
393 136 100 100     442 if (defined $min and defined $max) {
394 88 100       160 return 1 if $min > $max;
395             }
396 114         141 return 0;
397             }
398              
399             =item B
400              
401             Returns true if the interval is bound by an upper and lower limit.
402             An inverted interval would be bounded but inverted.
403              
404             =cut
405              
406             sub isbound {
407 92     92 1 66 my $self = shift;
408 92         117 my $min = $self->min;
409 92         120 my $max = $self->max;
410 92 100 100     228 if (defined $min and defined $max) {
411 52         65 return 1;
412             } else {
413 40         46 return 0;
414             }
415             }
416              
417              
418             =item B
419              
420             Compare with another Interval object.
421             Returns true if they are the same. False otherwise.
422              
423             =cut
424              
425             sub equate {
426 9     9 1 243 my $self = shift;
427 9         7 my $comparison = shift;
428              
429             # Need to check that both are objects
430 9 50       15 return 0 unless defined $comparison;
431 9 50       24 return 0 unless UNIVERSAL::isa($comparison, "Number::Interval");
432              
433             # need to be explicit about undefs
434             # return false immediately we find a difference
435 9         10 for my $m (qw/ min max/) {
436             # first values
437 15 100       20 if ( defined $comparison->$m() ) {
438 9 50       9 return 0 if !defined $self->$m();
439 9 100       12 return 0 if $comparison->$m() != $self->$m();
440             } else {
441 6 100       11 return 0 if defined $self->$m();
442             }
443              
444             # then boolean
445 13         18 my $incm = 'inc_' . $m;
446              
447             # return false if state of one is NOT the other
448 13 100 33     18 return 0 if ( ( $self->$incm() && !$comparison->$incm() ) ||
      66        
      33        
449             ( !$self->$incm() && $comparison->$incm() ) );
450             }
451 5         22 return 1;
452             }
453              
454             =item B
455              
456             Inverse of C. Used by the tied interface to implement !=.
457              
458             $i1 != $i2
459              
460             =cut
461              
462             sub notequal {
463 4     4 1 17 my $self = shift;
464 4         7 return !$self->equate( @_ );
465             }
466              
467             =item B
468              
469             Determine whether a supplied value is within the defined intervals.
470              
471             $is = $i->contains( $value );
472              
473             If both intervals are undefined, always returns true.
474              
475             B If the min == max, returns true if the supplied value is that
476             value, but only if either IncMin or IncMax is true.
477             I previously this method returned
478             true in this case regardless of IncMin and IncMax setttings.
479              
480             If the interval is positive definite, always returns false if the
481             supplied value is negative.
482              
483             =cut
484              
485             sub contains {
486 42     42 1 2824 my $self = shift;
487 42         42 my $value = shift;
488              
489 42         62 my $max = $self->max;
490 42         56 my $min = $self->min;
491 42 50 66     80 return 1 if (!defined $max && !defined $min);
492              
493             # Assume it doesnt match the interval
494 42         32 my $contains = 0;
495 42 100       124 if ($self->isinverted) {
496             # Inverted interval. Both max and min must be defined
497 6 50 33     20 if (defined $max and defined $min) {
498 6 50 33     10 if ($self->inc_max && $self->inc_min) {
    50          
    50          
499 0 0 0     0 if ($value <= $max || $value >= $min) {
500 0         0 $contains = 1;
501             }
502             } elsif ($self->inc_max) {
503 0 0 0     0 if ($value <= $max || $value > $min) {
504 0         0 $contains = 1;
505             }
506             } elsif ($self->inc_min) {
507 0 0 0     0 if ($value < $max || $value >= $min) {
508 0         0 $contains = 1;
509             }
510             } else {
511 6 100 100     24 if ($value < $max || $value > $min) {
512 4         5 $contains = 1;
513             }
514             }
515              
516             } else {
517 0         0 croak "An interval can not be inverted with only one defined value";
518             }
519              
520             } else {
521             # normal interval
522 36 100 100     134 if (defined $max and defined $min) {
    100          
    50          
523 28 100 100     72 if ($max == $min) { # need to include a bound
    100 100        
    100          
    100          
    100          
524 5 100 100     10 if ($self->inc_min || $self->inc_max) {
525 4 100       26 $contains = 1 if $value == $max;
526             }
527             } elsif ($self->pos_def && $value < 0) {
528 2         4 $contains = 0;
529             } elsif ($self->inc_max && $self->inc_min) {
530 2 50 33     15 if ($value <= $max && $value >= $min) {
531 2         3 $contains = 1;
532             }
533             } elsif ($self->inc_max) {
534 2 100 66     8 if ($value <= $max && $value > $min) {
535 1         1 $contains = 1;
536             }
537             } elsif ($self->inc_min) {
538 6 100 100     20 if ($value < $max && $value >= $min) {
539 3         5 $contains = 1;
540             }
541             } else {
542 11 100 100     32 if ($value < $max && $value > $min) {
543 5         6 $contains = 1;
544             }
545             }
546             } elsif (defined $max) {
547 4 100       12 if ($self->inc_max) {
548 1 50       4 $contains = 1 if $value <= $max;
549             } else {
550 3 100       8 $contains = 1 if $value < $max;
551             }
552             } elsif (defined $min) {
553 4 100       6 if ($self->inc_min) {
554 1 50       5 $contains = 1 if $value >= $min;
555             } else {
556 3 100       21 $contains = 1 if $value > $min;
557             }
558             }
559             }
560              
561 42         215 return $contains;
562             }
563              
564              
565             =item B
566              
567             Given another Interval object, modify the existing interval to include
568             the additional constraints. For example, if the current object
569             has a interval of -3 to 10, and it is merged with an external object
570             that has a interval of 0 to 20 then the interval of the current object
571             will be converted to 0 to 10 since that is consistent with both
572             intervals.
573              
574             $status = $interval->intersection( $newinterval );
575              
576             Returns true if the intersection was successful. If the intervals are
577             incompatible (no intersection) or if no object was supplied returns
578             false and the object is not modified.
579              
580             Intersections of an inverted interval with a non-inverted interval
581             can, in some circumstances, result in an intersection covering
582             two distinct bound intervals. This class can not yet support multiple
583             intervals (that would make the intersection method even more of a nightmare)
584             so the routine dies if such a situation arises.
585              
586             =cut
587              
588             # There must be a neater way of implementing this method!
589             # There may be some edge cases that fail (when one of the
590             # interval boundaries is identical in both objects)
591              
592             sub intersection {
593 46     46 1 226 my $self = shift;
594 46         36 my $int2 = shift;
595              
596             # Check input
597 46 50       78 return 0 unless defined $int2;
598 46 50       140 return 0 unless UNIVERSAL::isa($int2,"Number::Interval");
599              
600             # Create an alternate reference to $self to use in case of swapping
601             # the variables around.
602 46         36 my $int1 = $self;
603              
604             # Get the values
605 46         60 my $max1 = $int1->max;
606 46         77 my $min1 = $int1->min;
607 46         51 my $max2 = $int2->max;
608 46         56 my $min2 = $int2->min;
609              
610 46         57 my $inverted1 = $int1->isinverted;
611 46         55 my $inverted2 = $int2->isinverted;
612 46   66     91 my $inverted = $inverted1 || $inverted2;
613              
614 46         63 my $bound1 = $int1->isbound;
615 46         52 my $bound2 = $int2->isbound;
616 46   100     88 my $bound = $bound1 || $bound2;
617              
618 46         67 my $outmax;
619             my $outmin;
620              
621             # There are six possible combinations of Bound interval,
622             # inverted interval and unbound interval.
623              
624 46 100       59 if ($bound) {
625             # Support BB, BU and BI and II
626              
627 37 100       47 if ($inverted) {
628             # Any inverted: II or BI or IB or UI or IU
629             #print "*********** INVERTED *********\n";
630              
631 14 100 66     44 if ($inverted1 && $inverted2) {
632             # II
633             # This is fairly easy.
634             # Always take the smallest max and largest min
635 1 50       3 $outmin = ( $min1 > $min2 ? $min1 : $min2);
636 1 50       4 $outmax = ( $max1 < $max2 ? $max1 : $max2);
637              
638             } else {
639             # IB, IU (BI and UI)
640             # swap if needed, to have everything as IX
641 13         10 my $nowbound;
642 13 50       17 if ($inverted2) {
643 0         0 ($max1,$min1,$max2,$min2,$int1,$int2) =
644             ($max2,$min2,$max1,$min1,$int2,$int1);
645             # determine bound state of #1 before losing order information
646 0         0 $nowbound = $bound1;
647             } else {
648             # #1 is inverted so we need the bound state of #1
649 13         12 $nowbound = $bound2;
650             }
651              
652 13 100       17 if ($nowbound) {
653             # IB
654             # We know that max2 and min2 are defined
655             # We always end up with at least one bound interval
656 6 100       17 if ($min2 < $max1) {
    100          
    50          
657 3         2 $outmin = $min2;
658              
659             # If max2 is too high we get two intervals.
660 3 100       235 croak "This intersection results in two output intervals. Currently not supported" if $max2 > $min1;
661              
662             # Upper limit of interval must be the min of the two maxes
663 2 100       5 $outmax = ( $max1 < $max2 ? $max1 : $max2 );
664              
665             } elsif ($min2 < $min1) {
666              
667             # Make sure we intersect a little
668             # If the bound interval lies outside the inverted interval
669             # return undef
670 2 100       6 if ($max2 >= $min1) {
671 1         3 $outmin = $min1;
672 1         4 $outmax = $max2;
673             }
674              
675             } elsif ($min2 > $min1) {
676              
677             # This is just the bound interval
678 1         3 $outmin = $min2;
679 1         2 $outmax = $max2;
680              
681              
682             } else {
683 0         0 croak "Oops Bug in interval intersection [6]\n".
684             _formaterr( $min1, $max1, $min2, $max2);
685             }
686              
687              
688             } else {
689             # IU
690 7 100       16 if (defined $max2) {
    100          
691              
692             # The upper bound must be below the inverted "min"
693             # else we get intersection of two intervals
694 3 100       10 if ($max2 > $min1) {
    100          
695 1         131 croak "This intersection results in two output intervals. Currently not supported";
696             } elsif ($max2 > $max1) {
697             # Just use the inverted interval
698 1         2 $outmax = $max1;
699 1         2 $outmin = $min1;
700             } else {
701             # max must be decreased to include min2
702 1         2 $outmax = $max2;
703 1         1 $outmin = $min1;
704             }
705              
706              
707             } elsif (defined $min2) {
708              
709             # The lower bound must be above the "max"
710             # else we get an intersection of two intervals
711 3 100       13 if ($min2 < $max1) {
    100          
712 1         110 croak "This intersection results in two output intervals. Currently not supported";
713             } elsif ($min2 < $min1) {
714             # Just use the inverted interval
715 1         2 $outmax = $max1;
716 1         2 $outmin = $min1;
717             } else {
718             # min must be increased to include min2
719 1         2 $outmax = $max1;
720 1         2 $outmin = $min2;
721             }
722              
723             } else {
724             # both undefined
725 1         1 $outmax = $max1;
726 1         1 $outmin = $min1;
727             }
728              
729             }
730              
731             }
732              
733              
734              
735             } else {
736             # BB, BU or UB
737             #print "*********** BOUND NON INVERTED ************\n";
738 23 100 100     64 if ($bound1 and $bound2) {
739             # BB
740             #print "---------- BB -----------\n";
741 8 50       32 $outmin = ( $min1 > $min2 ? $min1 : $min2 );
742 8 100       11 $outmax = ( $max1 < $max2 ? $max1 : $max2 );
743              
744             # Check that we really are overlapping
745 8 100       19 if ($outmax < $outmin) {
746             # oops - intervals did not intersect. Reset
747 1         3 $outmin = $outmax = undef;
748             }
749            
750              
751             } else {
752             # BU and UB
753             #print "---------- BU/UB -----------\n";
754             # swap if needed, to have everything as BU
755 15 100       21 if ($bound2) {
756 6         13 ($max1,$min1,$max2,$min2,$int1,$int2) =
757             ($max2,$min2,$max1,$min1,$int2,$int1);
758             }
759              
760             # unbound is now guaranteed to be (2)
761             # Check that unbound max is in interval
762 15 100       29 if (defined $max2) {
    100          
763 8 100 66     36 if ($max2 <= $max1 && $max2 >= $min1) {
    50          
    50          
764             # inside interval
765 2         4 $outmax = $max2;
766 2         3 $outmin = $min1;
767             } elsif ($max2 <= $min1) {
768             # outside interval. No intersection
769             } elsif ($max2 >= $max1) {
770             # below interval. irrelevant
771 6         6 $outmax = $max1;
772 6         8 $outmin = $min1;
773             } else {
774 0         0 croak "Number::Interval - This should not happen[2]\n".
775             _formaterr( $min1, $max1, $min2, $max2);
776             }
777              
778             } elsif (defined $min2) {
779 5 100 100     22 if ($min2 <= $max1 && $min2 >= $min1) {
    100          
    50          
780             # inside interval
781 3         5 $outmax = $max1;
782 3         4 $outmin = $min2;
783             } elsif ($min2 >= $max1) {
784             # outside interval. No intersection
785             } elsif ($min2 <= $min1) {
786             # below interval. irrelevant
787 1         3 $outmax = $max1;
788 1         2 $outmin = $min1;
789             } else {
790 0         0 croak "Number::Interval - This should not happen[3]:\n" .
791             _formaterr( $min1, $max1, $min2, $max2);
792             }
793              
794             } else {
795             # The second interval is unbounded at both ends
796 2         3 $outmax = $max1;
797 2         2 $outmin = $min1;
798             }
799              
800              
801             }
802              
803             }
804              
805              
806             } else {
807             # Unbound+Unbound only
808             # Four options here.
809             # 1. A max and a max => max (same for min and min)
810             # 2. max and a min with no overlap => no intersection
811             # 3. max and min with overlap => bounded interval
812             # 4. all undefined
813 9 100 100     43 if (defined $max1 && defined $max2) {
    100 100        
814 1 50       5 $outmax = ( $max1 < $max2 ? $max1 : $max2 );
815             } elsif (defined $min2 && defined $min1) {
816 1 50       3 $outmin = ( $min1 > $min2 ? $min1 : $min2 );
817             } else {
818             # max and a min - one must be defined for both
819 7 100       9 my $refmax = (defined $max1 ? $max1 : $max2);
820 7 100       12 my $refmin = (defined $min1 ? $min1 : $min2);
821              
822 7 100 100     33 if (!defined $refmax && !defined $refmin) {
    100          
    100          
    100          
823             # infinite bound
824 1         4 return 1;
825             } elsif (!defined $refmax) {
826             # just a min
827 1         2 $outmin = $refmin;
828             } elsif (!defined $refmin) {
829             # just a max
830 1         2 $outmax = $refmax;
831             } elsif ($refmax > $refmin) {
832             # normal bound interval
833 2         3 $outmax = $refmax;
834 2         2 $outmin = $refmin;
835             } else {
836             # unbound interval. No intersection
837             }
838              
839              
840             }
841              
842             }
843              
844              
845             # Modify object if we have new values
846 42 100 100     90 if (defined $outmax or defined $outmin) {
847             # Need to check the inc_min and inc_max settings
848 37         57 my $inc_max = $self->_checkinc( $outmax, $max1, $max2,
849             $int1->inc_max, $int2->inc_max );
850 37         58 my $inc_min = $self->_checkinc( $outmin, $min1, $min2,
851             $int1->inc_min, $int2->inc_min );
852              
853             # Abort if the min and max are the same and we
854             # are not including the bounds in the interval
855 37 100 100     192 if (defined $outmax && defined $outmin &&
      100        
      66        
      100        
856             $outmax == $outmin &&
857             (!$inc_max || !$inc_min)
858             ) {
859 4         71 return 0;
860             }
861              
862 33         39 $self->inc_min( $inc_min );
863 33         35 $self->inc_max( $inc_max );
864 33         40 $self->max($outmax);
865 33         118 $self->min($outmin);
866 33         124 return 1;
867             } else {
868 5         19 return 0;
869             }
870              
871             }
872              
873             # Given
874             sub _checkinc {
875 74     74   53 my $self = shift;
876 74         54 my $newval = shift;
877 74         59 my $ref1 = shift;
878 74         77 my $ref2 = shift;
879 74         66 my $inc1 = shift;
880 74         56 my $inc2 = shift;
881              
882 74         47 my $inc_val = $inc1;
883 74 100       157 if (defined $newval) {
884 70 100 100     515 if (defined $ref1 && $ref1 == $newval &&
    100 100        
      100        
      100        
885             defined $ref2 && $ref2 == $newval) {
886             # value comes from both so we want the least
887             # inclusive inc_max value
888 4 50 66     124 $inc_val = 0 if (!$inc1 || !$inc2);
889             } elsif (defined $ref2 && $ref2 == $newval) {
890             # this value comes from ref2 so we copy
891             # inc from #2
892 23         21 $inc_val = $inc2;
893             }
894             }
895 74         74 return $inc_val;
896             }
897              
898             sub _formaterr {
899 0     0     my ($min1, $max1, $min2, $max2) = @_;
900 0 0         return "Comparing : (".
    0          
    0          
    0          
901             (defined $min1 ? $min1 : "" ).
902             "," .
903             (defined $max1 ? $max1 : "" ).
904             ") with (".
905             (defined $min2 ? $min2 : "" ).
906             "," . (defined $max2 ? $max2 : "" ).
907             ")";
908             }
909              
910             =back
911              
912             =head1 NOTES
913              
914             The default interval is not inclusive of the bounds.
915              
916             =head1 COPYRIGHT
917              
918             Copyright (C) 2009-2011 Science and Technology Facilities Council.
919             Copyright (C) 2002-2005 Particle Physics and Astronomy Research Council.
920             All Rights Reserved.
921              
922             This program is free software; you can redistribute it and/or modify it under
923             the terms of the GNU General Public License as published by the Free Software
924             Foundation; either version 2 of the License, or (at your option) any later
925             version.
926              
927             This program is distributed in the hope that it will be useful,but WITHOUT ANY
928             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
929             PARTICULAR PURPOSE. See the GNU General Public License for more details.
930              
931             You should have received a copy of the GNU General Public License along with
932             this program; if not, write to the Free Software Foundation, Inc., 59 Temple
933             Place,Suite 330, Boston, MA 02111-1307, USA
934              
935             =head1 AUTHOR
936              
937             Tim Jenness Etjenness@cpan.orgE.
938              
939             =cut
940              
941             1;
942