File Coverage

blib/lib/Types/Numbers.pm
Criterion Covered Total %
statement 68 70 97.1
branch 19 22 86.3
condition 7 12 58.3
subroutine 26 27 96.3
pod n/a
total 120 131 91.6


line stmt bran cond sub pod time code
1             package Types::Numbers;
2              
3             our $AUTHORITY = 'cpan:GSG';
4             # ABSTRACT: Type constraints for numbers
5 6     6   477695 use version;
  6         12215  
  6         35  
6             our $VERSION = 'v1.0.0'; # VERSION
7              
8             #############################################################################
9             # Modules
10              
11 6     6   636 use v5.8.8;
  6         45  
12 6     6   32 use strict;
  6         11  
  6         125  
13 6     6   29 use warnings;
  6         12  
  6         311  
14              
15             our @EXPORT_OK = ();
16              
17 6     6   3217 use Type::Library -base;
  6         132467  
  6         67  
18 6     6   5290 use Type::Tiny::Intersection;
  6         8159  
  6         200  
19 6     6   3194 use Type::Tiny::Union;
  6         13110  
  6         245  
20 6     6   3850 use Types::Standard v0.030 ('Bool'); # support for Error::TypeTiny
  6         320685  
  6         70  
21              
22 6     6   5515 use Scalar::Util 1.20 (qw(blessed looks_like_number)); # support for overloaded/blessed looks_like_number
  6         233  
  6         456  
23 6     6   3506 use POSIX 'ceil';
  6         41121  
  6         39  
24 6     6   16386 use Math::BigInt 1.92; # somewhat a stab in the dark for a passable version
  6         158632  
  6         41  
25 6     6   151920 use Math::BigFloat 1.65; # earliest version that passes tests
  6         170696  
  6         65  
26 6     6   9190 use Data::Float;
  6         53432  
  6         411  
27 6     6   3995 use Data::Integer;
  6         57487  
  6         567  
28              
29             use constant {
30 6         42057 _BASE2_LOG => log(2) / log(10),
31 6     6   57 };
  6         14  
32              
33 0     0   0 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  0         0  
34              
35             #pod =encoding utf8
36             #pod
37             #pod =head1 DESCRIPTION
38             #pod
39             #pod Because we deal with numbers every day in our programs and modules, this is an extensive
40             #pod L library of number validations. Like L, these types work with
41             #pod all modern OO platforms and as a standalone type system.
42             #pod
43             #pod =cut
44              
45             #############################################################################
46             # Basic globals
47              
48             my $bigtwo = Math::BigFloat->new(2);
49             my $bigten = Math::BigFloat->new(10);
50              
51             # Large 64-bit floats (long doubles) tend to stringify themselves in exponent notation, even
52             # though the number is still pristine. IOW, the numeric form is perfect, but the string form
53             # loses information. This can be a problem for stringified inlines.
54             my @df_max_int_parts = Data::Float::float_parts( Data::Float::max_integer );
55             my $DF_MAX_INT = $bigtwo->copy->bpow($df_max_int_parts[1])->bmul($df_max_int_parts[2])->as_int;
56              
57             my $SAFE_NUM_MIN = Math::BigInt->new(
58             Data::Integer::min_signed_natint < $DF_MAX_INT * -1 ?
59             Data::Integer::min_signed_natint : $DF_MAX_INT * -1
60             );
61             my $SAFE_NUM_MAX = Math::BigInt->new(
62             Data::Integer::max_unsigned_natint > $DF_MAX_INT * 1 ?
63             Data::Integer::max_unsigned_natint : $DF_MAX_INT * 1,
64             );
65              
66             my $meta = __PACKAGE__->meta;
67              
68             #############################################################################
69             # Framework types
70              
71             ### TODO: Coercions where safe ###
72              
73             #pod =head1 TYPES
74             #pod
75             #pod =head2 Overview
76             #pod
77             #pod All of these types strive for the accurate storage and validation of many different types of
78             #pod numbers, including some storage types that Perl doesn't natively support.
79             #pod
80             #pod The hierarchy of the types is as follows:
81             #pod
82             #pod (T:S = From Types::Standard)
83             #pod (~T:C:N = Based on Types::Common::Numeric types)
84             #pod
85             #pod Item (T:S)
86             #pod Defined (T:S)
87             #pod NumLike
88             #pod NumRange[`n, `p] (~T:C:N)
89             #pod PositiveNum (~T:C:N)
90             #pod PositiveOrZeroNum (~T:C:N)
91             #pod NegativeNum (~T:C:N)
92             #pod NegativeOrZeroNum (~T:C:N)
93             #pod IntLike
94             #pod SignedInt[`b]
95             #pod UnsignedInt[`b]
96             #pod IntRange[`n, `p] (~T:C:N)
97             #pod PositiveInt (~T:C:N)
98             #pod PositiveOrZeroInt (~T:C:N)
99             #pod NegativeInt (~T:C:N)
100             #pod NegativeOrZeroInt (~T:C:N)
101             #pod SingleDigit (~T:C:N)
102             #pod PerlNum
103             #pod PerlSafeInt
104             #pod PerlSafeFloat
105             #pod BlessedNum[`d]
106             #pod BlessedInt[`d]
107             #pod BlessedFloat[`d]
108             #pod NaN
109             #pod Inf[`s]
110             #pod FloatSafeNum
111             #pod FloatBinary[`b, `e]
112             #pod FloatDecimal[`d, `e]
113             #pod RealNum
114             #pod RealSafeNum
115             #pod FixedBinary[`b, `s]
116             #pod FixedDecimal[`d, `s]
117             #pod
118             #pod Value (T:S)
119             #pod Str (T:S)
120             #pod Char[`b]
121             #pod
122             #pod =head2 Basic types
123             #pod
124             #pod =head3 NumLike
125             #pod
126             #pod Behaves like C from L, but will also accept blessed number types. Unlike
127             #pod C, it will accept C and C numbers.
128             #pod
129             #pod =cut
130              
131             # Moose and Type::Tiny types both don't seem to support Math::Big* = Num.
132             # So, we have to start almost from stratch.
133             my $_NumLike = $meta->add_type(
134             name => 'NumLike',
135             parent => Types::Standard::Defined,
136             library => __PACKAGE__,
137             constraint => sub { looks_like_number $_ },
138             inlined => sub { "Scalar::Util::looks_like_number($_[1])" },
139             );
140              
141             #pod =head3 NumRange[`n, `p]
142             #pod
143             #pod Only accepts numbers within a certain range. By default, the two parameters are the minimums and maximums,
144             #pod inclusive. However, this type is also compatible with a few different parameter styles, a la L.
145             #pod
146             #pod The minimum/maximums can be omitted or undefined. Or two extra boolean parameters can be added to specify exclusivity:
147             #pod
148             #pod NumRange[0.1, 10.0, 0, 0] # both inclusive
149             #pod NumRange[0.1, 10.0, 0, 1] # exclusive maximum, so 10.0 is invalid
150             #pod NumRange[0.1, 10.0, 1, 0] # exclusive minimum, so 0.1 is invalid
151             #pod NumRange[0.1, 10.0, 1, 1] # both exclusive
152             #pod
153             #pod NumRange[0.1] # lower bound check only
154             #pod NumRange[undef, 10.0] # upper bound check only
155             #pod NumRange[0.1, undef, 1] # lower bound check only, exclusively
156             #pod NumRange[undef, 10.0, 1, 1] # upper bound check only, exclusively (third param ignored)
157             #pod
158             #pod =cut
159              
160             my $_NumRange = $meta->add_type(
161             name => 'NumRange',
162             parent => $_NumLike,
163             library => __PACKAGE__,
164             # kinda pointless without the parameters
165             constraint_generator => sub {
166             my $self = $Type::Tiny::parameterize_type;
167             my ($min, $max, $min_excl, $max_excl) = @_;
168             !defined $min or looks_like_number($min) or _croak( "First parameter to NumRange[`n, `p] expected to be a number; got $min");
169             !defined $max or looks_like_number($max) or _croak("Second parameter to NumRange[`n, `p] expected to be a number; got $max");
170             !defined $min_excl or Bool->check($min_excl) or _croak( "Third parameter to NumRange[`n, `p] expected to be a boolean; got $min_excl");
171             !defined $max_excl or Bool->check($max_excl) or _croak("Fourth parameter to NumRange[`n, `p] expected to be a boolean; got $max_excl");
172              
173             $min_excl = 0 unless defined $min_excl;
174             $max_excl = 0 unless defined $max_excl;
175              
176             my ($Imin, $Imax) = ($min, $max);
177             $Imin = blessed($min)."\->new('$min')" if defined $min && blessed $min;
178             $Imax = blessed($max)."\->new('$max')" if defined $max && blessed $max;
179              
180             my $display_name = 'NumRange['.
181             join(', ', map { defined $_ ? $_ : 'undef' } ($min, $max, $min_excl, $max_excl) ).
182             ']';
183              
184             Type::Tiny->new(
185             display_name => $display_name,
186             parent => $self,
187             library => __PACKAGE__,
188             constraint => sub {
189             my $val = $_;
190              
191             # AND checks, so return false on the logically-opposite checks (>= --> <)
192             if (defined $min) {
193             return !!0 if $val < $min;
194             return !!0 if $val == $min && $min_excl;
195             }
196             if (defined $max) {
197             return !!0 if $val > $max;
198             return !!0 if $val == $max && $max_excl;
199             }
200              
201             # NaN still passes both NumLike and the anti-checks above, so we use this seemingly-paradoxical
202             # logical check here to reject it
203             return $val == $val;
204             },
205             inlined => sub {
206             my ($self, $val) = @_;
207             my @checks = (undef); # parent check
208             push @checks, join(' ', $val, ($min_excl ? '>' : '>='), $Imin) if defined $min;
209             push @checks, join(' ', $val, ($max_excl ? '<' : '<='), $Imax) if defined $max;
210             @checks;
211             },
212             );
213             },
214             );
215              
216             # we need to optimize out all of the NumLike checks
217             my $_NumRange_perlsafe = Type::Tiny->new(
218             display_name => "_NumRange_perlsafe",
219             parent => $_NumLike,
220             # no equals because MAX+1 = MAX after truncation
221             constraint => sub { $_ > $SAFE_NUM_MIN && $_ < $SAFE_NUM_MAX },
222             inlined => sub { "$_ > ".$SAFE_NUM_MIN, "$_ < ".$SAFE_NUM_MAX },
223             );
224              
225             #pod =head3 PerlNum
226             #pod
227             #pod Exactly like C, but with a different parent. Only accepts unblessed numbers.
228             #pod
229             #pod =cut
230              
231             my $_PerlNum = $meta->add_type(
232             name => 'PerlNum',
233             parent => $_NumLike,
234             library => __PACKAGE__,
235              
236             # LaxNum has parental constraints that matter, so we can't just blindly steal its own
237             # constraint by itself. The inlined sub, OTOH, is self-sufficient.
238             constraint => sub { Types::Standard::LaxNum->check($_) },
239             inlined => Types::Standard::LaxNum->inlined,
240             );
241              
242             #pod =head3 BlessedNum
243             #pod
244             #pod Only accepts blessed numbers. A blessed number would be using something like L or
245             #pod L. It doesn't directly C check those classes, just that the number is
246             #pod blessed.
247             #pod
248             #pod =head3 BlessedNum[`d]
249             #pod
250             #pod A blessed number that supports at least certain amount of digit accuracy. The blessed number must
251             #pod support the C or C method.
252             #pod
253             #pod For example, C would work for the default settings of L, and supports
254             #pod numbers at least as big as 128-bit integers.
255             #pod
256             #pod =cut
257              
258             my $_BlessedNum = $meta->add_type( Type::Tiny::Intersection->new(
259             name => 'BlessedNum',
260             display_name => 'BlessedNum',
261             library => __PACKAGE__,
262             type_constraints => [ $_NumLike, Types::Standard::Object ],
263             constraint_generator => sub {
264             my $self = $Type::Tiny::parameterize_type;
265             my $digits = shift;
266             $digits =~ /\A[0-9]+\z/ or _croak("Parameter to BlessedNum[`d] expected to be a positive integer; got $digits");
267              
268             Type::Tiny->new(
269             display_name => "BlessedNum[$digits]",
270             parent => $self,
271             library => __PACKAGE__,
272             constraint => sub {
273             my $val = $_;
274              
275             $val->can('accuracy') && $val->accuracy && $val->accuracy >= $digits ||
276             $val->can('div_scale') && $val->div_scale && $val->div_scale >= $digits;
277             },
278             inlined => sub {
279             my ($self, $val) = @_;
280              
281             return (undef,
282             "$val->can('accuracy') && $val->accuracy && $val->accuracy >= $digits || ".
283             "$val->can('div_scale') && $val->div_scale && $val->div_scale >= $digits"
284             );
285             },
286             );
287             },
288             ) );
289              
290             #pod =head3 NaN
291             #pod
292             #pod A "not-a-number" value, either embedded into the Perl native float or a blessed C,
293             #pod checked via C.
294             #pod
295             #pod =cut
296              
297             my $_NaN = $meta->add_type(
298             name => 'NaN',
299             parent => $_NumLike,
300             library => __PACKAGE__,
301             constraint => sub {
302             my $val = $_;
303              
304             Types::Standard::Object->check($val) && $val->can('is_nan') && $val->is_nan ||
305             Data::Float::float_is_nan($val);
306             },
307             inlined => sub {
308             my ($self, $val) = @_;
309             return (undef,
310             Types::Standard::Object->inline_check($val)." && $val->can('is_nan') && $val->is_nan ||".
311             "Data::Float::float_is_nan($val)"
312             );
313             },
314             );
315              
316             #pod =head3 Inf
317             #pod
318             #pod An infinity value, either embedded into the Perl native float or a blessed C, checked via
319             #pod C.
320             #pod
321             #pod =head3 Inf[`s]
322             #pod
323             #pod Inf['+']
324             #pod Inf['-']
325             #pod
326             #pod An infinity value with a certain sign, either embedded into the Perl native float or a blessed
327             #pod C, checked via C. The parameter must be a plus or minus character.
328             #pod
329             #pod =cut
330              
331             my $_Inf = $meta->add_type(
332             name => 'Inf',
333             parent => $_NumLike,
334             library => __PACKAGE__,
335             constraint => sub {
336             my $val = $_;
337              
338             Types::Standard::Object->check($val) && $val->can('is_inf') && ($val->is_inf('+') || $val->is_inf('-')) ||
339             Data::Float::float_is_infinite($val);
340             },
341             inlined => sub {
342             my ($self, $val) = @_;
343             return (undef,
344             Types::Standard::Object->inline_check($val)." && $val->can('is_inf') && ($val->is_inf('+') || $val->is_inf('-')) ||".
345             "Data::Float::float_is_infinite($val)"
346             );
347             },
348             constraint_generator => sub {
349             my $self = $Type::Tiny::parameterize_type;
350             my $sign = shift;
351             $sign =~ /\A[+\-]\z/ or _croak("Parameter to Inf[`s] expected to be a plus or minus sign; got $sign");
352              
353             Type::Tiny->new(
354             display_name => "Inf[$sign]",
355             parent => $self,
356             library => __PACKAGE__,
357             constraint => sub {
358             my $val = $_;
359              
360             Types::Standard::Object->check($val) && $val->can('is_inf') && $val->is_inf($sign) ||
361             Data::Float::float_is_infinite($val) && Data::Float::float_sign($val) eq $sign;
362             },
363             inlined => sub {
364             my ($self, $val) = @_;
365              
366             return (undef,
367             Types::Standard::Object->inline_check($val)." && $val->can('is_inf') && $val->is_inf('$sign') || ".
368             "Data::Float::float_is_infinite($val) && Data::Float::float_sign($val) eq '$sign'"
369             );
370             },
371             );
372             },
373             );
374              
375             # this is used a lot for floats, but we need to optimize out all of the NumLike checks
376             my $_NaNInf = Type::Tiny::Union->new(
377             type_constraints => [ $_NaN, $_Inf ],
378             )->create_child_type(
379             name => 'NaNInf',
380             constraint => sub {
381             # looks_like_number($_) &&
382             Types::Standard::Object->check($_) && (
383             $_->can('is_nan') && $_->is_nan ||
384             $_->can('is_inf') && ($_->is_inf('+') || $_->is_inf('-'))
385             ) || Data::Float::float_is_nan($_) || Data::Float::float_is_infinite($_)
386             },
387             inlined => sub {
388             my ($self, $val) = @_;
389             # looks_like_number($val) &&
390             Types::Standard::Object->inline_check($val)." && ( ". # NOTE: A && (B) || C || D, so don't list-separate
391             "$val->can('is_nan') && $val->is_nan || ".
392             "$val->can('is_inf') && ($val->is_inf('+') || $val->is_inf('-')) ".
393             ") || Data::Float::float_is_nan($val) || Data::Float::float_is_infinite($val)";
394             },
395             );
396              
397             my $_not_NaNInf = $_NaNInf->complementary_type;
398              
399             #pod =head3 RealNum
400             #pod
401             #pod Like L, but does not accept NaN or Inf. Closer to the spirit of C, but
402             #pod accepts blessed numbers as well.
403             #pod
404             #pod =cut
405              
406             my $_RealNum = $meta->add_type( Type::Tiny::Intersection->new(
407             name => 'RealNum',
408             display_name => 'RealNum',
409             library => __PACKAGE__,
410             type_constraints => [ $_NumLike, $_not_NaNInf ],
411             ) );
412              
413             #############################################################################
414             # Integer types
415              
416             #pod =head2 Integers
417             #pod
418             #pod =cut
419              
420             # Helper subs
421             sub __integer_bits_vars {
422 12     12   24 my ($bits, $is_unsigned) = @_;
423              
424 12         22 my $sbits = $bits - 1;
425              
426 12         47 my ($neg, $spos, $upos) = (
427             $bigtwo->copy->bpow($sbits)->bmul(-1),
428             $bigtwo->copy->bpow($sbits)->bsub(1),
429             $bigtwo->copy->bpow( $bits)->bsub(1),
430             );
431 12         30206 my $sdigits = ceil( $sbits * _BASE2_LOG );
432 12         32 my $udigits = ceil( $bits * _BASE2_LOG );
433              
434 12 100       72 return $is_unsigned ?
435             (0, $upos, $udigits) :
436             ($neg, $spos, $sdigits)
437             ;
438             }
439              
440             #pod =head3 IntLike
441             #pod
442             #pod Behaves like C from L, but will also accept blessed number types and integers
443             #pod in E notation. There are no expectations of storage limitations here. (See L for
444             #pod that.)
445             #pod
446             #pod =cut
447              
448             ### XXX: This string equality check is necessary because Math::BigInt seems to think 1.5 == 1.
449             ### However, this is problematic with long doubles that stringify into E notation.
450             my $_IntLike = $meta->add_type(
451             name => 'IntLike',
452             parent => $_NumLike,
453             library => __PACKAGE__,
454             constraint => sub { /\d+/ && int($_) == $_ && (int($_) eq $_ || !ref($_)) },
455             inlined => sub {
456             my ($self, $val) = @_;
457             (undef, "$val =~ /\\d+/", "int($val) == $val", "(int($val) eq $val || !ref($val))");
458             },
459             );
460              
461             #pod =head3 IntRange[`n, `p]
462             #pod
463             #pod Only accepts integers within a certain range. By default, the two parameters are the minimums and maximums,
464             #pod inclusive. Though, the minimum/maximums can be omitted or undefined.
465             #pod
466             #pod =cut
467              
468             my $_IntRange = $meta->add_type(
469             name => 'IntRange',
470             parent => $_IntLike,
471             library => __PACKAGE__,
472             # kinda pointless without the parameters
473             constraint_generator => sub {
474             my $self = $Type::Tiny::parameterize_type;
475             my ($min, $max) = @_;
476             !defined $min or looks_like_number($min) or _croak( "First parameter to IntRange[`n, `p] expected to be a number; got $min");
477             !defined $max or looks_like_number($max) or _croak("Second parameter to IntRange[`n, `p] expected to be a number; got $max");
478              
479             my ($Imin, $Imax) = ($min, $max);
480             $Imin = blessed($min)."\->new('$min')" if defined $min && blessed $min;
481             $Imax = blessed($max)."\->new('$max')" if defined $max && blessed $max;
482              
483             my $display_name = 'IntRange['.
484             join(', ', map { defined $_ ? $_ : 'undef' } ($min, $max) ).
485             ']';
486              
487             Type::Tiny->new(
488             display_name => $display_name,
489             parent => $self,
490             library => __PACKAGE__,
491             constraint => sub {
492             my $val = $_;
493              
494             # AND checks, so return false on the logically-opposite checks (>= --> <)
495             return !!0 if defined $min && $val < $min;
496             return !!0 if defined $max && $val > $max;
497             return !!1;
498             },
499             inlined => sub {
500             my ($self, $val) = @_;
501             my @checks = (undef); # parent check
502             push @checks, "$val >= $Imin" if defined $min;
503             push @checks, "$val <= $Imax" if defined $max;
504             @checks;
505             },
506             );
507             },
508             );
509              
510             #pod =head3 PerlSafeInt
511             #pod
512             #pod A Perl (unblessed) integer number than can safely hold the integer presented. This varies between
513             #pod 32-bit and 64-bit versions of Perl.
514             #pod
515             #pod For example, for most 32-bit versions of Perl, the largest integer than can be safely held in a
516             #pod 4-byte NV (floating point number) is C<9007199254740992>. Numbers can go higher than that, but due
517             #pod to the NV's mantissa length (accuracy), information is lost beyond this point.
518             #pod
519             #pod In this case, C<...992> would pass and C<...993> would fail.
520             #pod
521             #pod (Technically, the max integer is C<...993>, but we can't tell the difference between C<...993> and
522             #pod C<...994>, so the cut off point is C<...992>, inclusive.)
523             #pod
524             #pod Be aware that Perls compiled with "long doubles" have a unique problem with storage and information
525             #pod loss: their number form maintains accuracy while their (default) stringified form loses
526             #pod information. For example, take the max safe integer for a long double:
527             #pod
528             #pod $num = 18446744073709551615;
529             #pod say $num; # 1.84467440737095516e+19
530             #pod say $num == 18446744073709551615; # true, so the full number is still there
531             #pod say sprintf('%u', $num); # 18446744073709551615
532             #pod
533             #pod These numbers are considered safe for storage. If this is not preferred, consider a simple C
534             #pod check for stringified E notation.
535             #pod
536             #pod =cut
537              
538             my $_PerlSafeInt = $meta->add_type( Type::Tiny::Intersection->new(
539             library => __PACKAGE__,
540             type_constraints => [ $_PerlNum, $_IntLike, $_NumRange_perlsafe ],
541             )->create_child_type(
542             name => 'PerlSafeInt',
543             library => __PACKAGE__,
544             inlined => sub {
545             my $val = $_[1];
546             ("defined $val", "!ref($val)", "$val =~ /\\d+/", "int($val) == $val", $_NumRange_perlsafe->inline_check($val));
547             },
548             ) );
549              
550             #pod =head3 BlessedInt
551             #pod
552             #pod A blessed number than is holding an integer. (A L with an integer value would
553             #pod still pass.)
554             #pod
555             #pod =head3 BlessedInt[`d]
556             #pod
557             #pod A blessed number holding an integer of at most C<`d> digits (inclusive). The blessed number
558             #pod container must also have digit accuracy to support this number. (See L.)
559             #pod
560             #pod =cut
561              
562             my $_BlessedInt = $meta->add_type( Type::Tiny::Intersection->new(
563             library => __PACKAGE__,
564             type_constraints => [ $_BlessedNum, $_IntLike ],
565             )->create_child_type(
566             name => 'BlessedInt',
567             library => __PACKAGE__,
568             inlined => sub {
569             my $val = $_[1];
570             Types::Standard::Object->inline_check($val), "$val =~ /\\d+/", "int($val) == $val", "int($val) eq $val";
571             },
572             constraint_generator => sub {
573             my $self = $Type::Tiny::parameterize_type;
574             my $digits = shift;
575             $digits =~ /\A[0-9]+\z/ or _croak("Parameter to BlessedInt[`d] expected to be a positive integer; got $digits");
576              
577             my $_BlessedNum_param = $_BlessedNum->parameterize($digits);
578              
579             Type::Tiny->new(
580             display_name => "BlessedInt[$digits]",
581             parent => $self,
582             library => __PACKAGE__,
583             constraint => sub {
584             $_IntLike->check($_) && $_BlessedNum_param->check($_) && do {
585             my $num = $_;
586             $num =~ s/\D+//g;
587             length($num) <= $digits
588             }
589             },
590             inlined => sub {
591             my $val = $_[1];
592             return (
593             $_BlessedNum_param->inline_check($val),
594             "$val =~ /\\d+/", "int($val) == $val", "int($val) eq $val",
595             "do { ".
596             'my $num = '.$val.'; '.
597             '$num =~ s/\D+//g; '.
598             'length($num) <= '.$digits.' '.
599             '}'
600             );
601             },
602             );
603             },
604             ) );
605              
606             #pod =head3 SignedInt
607             #pod
608             #pod A signed integer (blessed or otherwise) that can safely hold its own number. This is different
609             #pod than L, which doesn't check for storage limitations.
610             #pod
611             #pod =head3 SignedInt[`b]
612             #pod
613             #pod A signed integer that can hold a C<`b> bit number and is within those boundaries. One bit is
614             #pod reserved for the sign, so the max limit on a 32-bit integer is actually C<2**31-1> or
615             #pod C<2147483647>.
616             #pod
617             #pod =cut
618              
619             $meta->add_type( Type::Tiny::Union->new(
620             #parent => $_IntLike,
621             library => __PACKAGE__,
622             type_constraints => [ $_PerlSafeInt, $_BlessedInt ],
623             )->create_child_type(
624             name => 'SignedInt',
625             library => __PACKAGE__,
626             inlined => sub {
627             my $val = $_[1];
628             return (
629             $_IntLike->inline_check($val),
630             $_NumRange_perlsafe->inline_check($val).' || '.Types::Standard::Object->inline_check($val)
631             );
632             },
633             constraint_generator => sub {
634             my $self = $Type::Tiny::parameterize_type;
635             my $bits = shift;
636             $bits =~ /\A[0-9]+\z/ or _croak("Parameter to SignedInt[`b] expected to be a positive integer; got $bits");
637              
638             my ($min, $max, $digits) = __integer_bits_vars($bits, 0);
639             my $_BlessedInt_param = $_BlessedInt->parameterize($digits);
640             my $_NumRange_param = $_NumRange ->parameterize($min, $max);
641              
642             Type::Tiny::Intersection->new(
643             library => __PACKAGE__,
644             type_constraints => [ $self, ($_PerlSafeInt|$_BlessedInt_param), $_NumRange_param ],
645             )->create_child_type(
646             display_name => "SignedInt[$bits]",
647             inlined => sub {
648             my $val = $_[1];
649             return (
650             $_PerlSafeInt->inline_check($val).' || '.$_BlessedInt_param->inline_check($val),
651             $_NumRange_param->inline_check($val)
652             );
653             },
654             );
655             },
656             ) );
657              
658             #pod =head3 UnsignedInt
659             #pod
660             #pod Like L, but with a minimum boundary of zero.
661             #pod
662             #pod =head3 UnsignedInt[`b]
663             #pod
664             #pod Like L, but for unsigned integers. Also, unsigned integers gain their extra bit,
665             #pod so the maximum is twice as high.
666             #pod
667             #pod =cut
668              
669             $meta->add_type(
670             name => 'UnsignedInt',
671             parent => $_IntLike,
672             library => __PACKAGE__,
673             constraint => sub { $_IntLike->check($_) && $_ >= 0 && ($_PerlSafeInt->check($_) || $_BlessedNum->check($_)) },
674             inlined => sub {
675             my $val = $_[1];
676             (undef, "$val >= 0", '('.
677             $_NumRange_perlsafe->inline_check($val).' || '.Types::Standard::Object->inline_check($val).
678             ')');
679             },
680             constraint_generator => sub {
681             my $self = $Type::Tiny::parameterize_type;
682             my $bits = shift;
683             $bits =~ /\A[0-9]+\z/ or _croak("Parameter to UnsignedInt[`b] expected to be a positive integer; got $bits");
684              
685             my ($min, $max, $digits) = __integer_bits_vars($bits, 1);
686             my $_BlessedNum_param = $_BlessedNum->parameterize($digits); # IntLike check extracted out
687             my $_NumRange_param = $_NumRange ->parameterize($min, $max);
688              
689             # inline will already have the IntLike check, and maybe not need the extra NumRange check
690             my $perlsafe_inline = $min >= $SAFE_NUM_MIN && $max <= $SAFE_NUM_MAX ?
691             sub { Types::Standard::Str->inline_check($_[0]) } :
692             sub { '('.Types::Standard::Str->inline_check($_[0]).' && '.$_NumRange_perlsafe->inline_check($_[0]).')' }
693             ;
694              
695             Type::Tiny->new(
696             display_name => "UnsignedInt[$bits]",
697             parent => $self,
698             library => __PACKAGE__,
699             constraint => sub {
700             $_IntLike->check($_) && $_NumRange_param->check($_) &&
701             ($_PerlSafeInt->check($_) || $_BlessedNum_param->check($_));
702             },
703             inlined => sub {
704             my $val = $_[1];
705             return (
706             $_IntLike->inline_check($val),
707             $_NumRange_param->inline_check($val),
708             $perlsafe_inline->($val).' || '.$_BlessedNum_param->inline_check($val)
709             );
710             },
711             );
712             },
713             );
714              
715             #############################################################################
716             # Float/fixed types
717              
718             #pod =head2 Floating-point numbers
719             #pod
720             #pod =head3 PerlSafeFloat
721             #pod
722             #pod A Perl native float that is in the "integer safe" range, or is a NaN/Inf value.
723             #pod
724             #pod This doesn't guarantee that every single fractional number is going to retain all of its
725             #pod information here. It only guarantees that the whole number will be retained, even if the
726             #pod fractional part is partly or completely lost.
727             #pod
728             #pod =cut
729              
730             my $_PerlSafeFloat = $meta->add_type(
731             name => 'PerlSafeFloat',
732             parent => $_PerlNum,
733             library => __PACKAGE__,
734             constraint => sub { $_NumRange_perlsafe->check($_) || Data::Float::float_is_nan($_) || Data::Float::float_is_infinite($_) },
735             inlined => sub {
736             my ($self, $val) = @_;
737             return (undef,
738             $_NumRange_perlsafe->inline_check($val)." || Data::Float::float_is_nan($val) || Data::Float::float_is_infinite($val)"
739             );
740             },
741             );
742              
743             #pod =head3 BlessedFloat
744             #pod
745             #pod A blessed number that will support fractional numbers. A L number will pass,
746             #pod whereas a L number will fail. However, if that L number is capable of
747             #pod upgrading to a L, it will pass.
748             #pod
749             #pod =head3 BlessedFloat[`d]
750             #pod
751             #pod A float-capable blessed number that supports at least certain amount of digit accuracy. The number
752             #pod itself is not boundary checked, as it is excessively difficult to figure out the exact dimensions
753             #pod of a floating point number. It would also not be useful for numbers like C<0.333333...> to fail
754             #pod checks.
755             #pod
756             #pod =cut
757              
758             my $_BlessedFloat = $meta->add_type(
759             name => 'BlessedFloat',
760             parent => $_BlessedNum,
761             library => __PACKAGE__,
762             constraint => sub { blessed($_)->new(1.2) == 1.2 },
763             inlined => sub {
764             my ($self, $val) = @_;
765             undef, "Scalar::Util::blessed($val)\->new(1.2) == 1.2";
766             },
767             constraint_generator => sub {
768             my $self = $Type::Tiny::parameterize_type;
769             my $digits = shift;
770             $digits =~ /\A[0-9]+\z/ or _croak("Parameter to BlessedFloat[`d] expected to be a positive integer; got $digits");
771              
772             my $_BlessedNum_param = $_BlessedNum->parameterize($digits);
773              
774             Type::Tiny->new(
775             display_name => "BlessedFloat[$digits]",
776             parent => $self,
777             library => __PACKAGE__,
778             constraint => sub { $_BlessedNum_param->check($_) && blessed($_)->new(1.2) == 1.2 },
779             inlined => sub {
780             my ($self, $val) = @_;
781             ($_BlessedNum_param->inline_check($val), "Scalar::Util::blessed($val)\->new(1.2) == 1.2");
782             },
783             );
784             },
785             );
786              
787             #pod =head3 FloatSafeNum
788             #pod
789             #pod A Union of L and L. In other words, a float-capable number with
790             #pod some basic checks to make sure information is retained.
791             #pod
792             #pod =cut
793              
794             my $_FloatSafeNum = $meta->add_type( Type::Tiny::Union->new(
795             library => __PACKAGE__,
796             type_constraints => [ $_PerlSafeFloat, $_BlessedFloat ],
797             )->create_child_type(
798             name => 'FloatSafeNum',
799             library => __PACKAGE__,
800             inlined => sub {
801             my ($self, $val) = @_;
802             return (
803             undef,
804             "!ref($val)",
805             "Scalar::Util::blessed($val)->new(1.2) == 1.2",
806             '('.
807             $_NumRange_perlsafe->inline_check($val)." || Data::Float::float_is_nan($val) || Data::Float::float_is_infinite($val)".
808             ') || '.Types::Standard::Object->inline_check($val),
809             );
810             },
811             ) );
812              
813             #pod =head3 FloatBinary[`b, `e]
814             #pod
815             #pod A floating-point number that can hold a C<`b> bit number with C<`e> bits of exponent, and is within
816             #pod those boundaries (or is NaN/Inf). The bit breakdown follows traditional IEEE 754 floating point
817             #pod standards. For example:
818             #pod
819             #pod FloatBinary[32, 8] =
820             #pod 32 bits total (`b)
821             #pod 23 bit mantissa (significand precision)
822             #pod 8 bit exponent (`e)
823             #pod 1 bit sign (+/-)
824             #pod
825             #pod Unlike the C<*Int> types, if Perl's native NV cannot support all dimensions of the floating-point
826             #pod number without losing information, then unblessed numbers are completely off the table. For
827             #pod example, assuming a 32-bit machine:
828             #pod
829             #pod (UnsignedInt[64])->check( 0 ) # pass
830             #pod (UnsignedInt[64])->check( 2 ** 30 ) # pass
831             #pod (UnsignedInt[64])->check( 2 ** 60 ) # fail, because 32-bit NVs can't safely hold it
832             #pod
833             #pod (FloatBinary[64, 11])->check( 0 ) # fail
834             #pod (FloatBinary[64, 11])->check( $any_unblessed_number ) # fail
835             #pod
836             #pod =cut
837              
838             ### NOTE: These two are very close to another type, but there's just too many variables
839             ### to throw into a typical type
840              
841             sub __real_constraint_generator {
842 24     24   97 my ($is_perl_safe, $digits, $_NumRange_param, $no_naninf) = @_;
843 24         92 my $_BlessedFloat_param = $_BlessedFloat->parameterize($digits);
844              
845 24 100       10901 if ($no_naninf) {
846             return $is_perl_safe ?
847 162 50 33 162   216787 sub { ( $_PerlNum->check($_) || $_BlessedFloat_param->check($_) ) && $_NumRange_param->check($_) } :
848 162 50   162   213072 sub { $_BlessedFloat_param->check($_) && $_NumRange_param->check($_) }
849 12 100       81 ;
850             }
851             else {
852             return $is_perl_safe ?
853 180 100 33 180   185940 sub { ( $_PerlNum->check($_) || $_BlessedFloat_param->check($_) ) && $_NumRange_param->check($_) || $_NaNInf->check($_) } :
      66        
854 180 50 100 180   183659 sub { $_BlessedFloat_param->check($_) && ( $_NumRange_param->check($_) || $_NaNInf->check($_) ); }
855 12 100       134 ;
856             }
857             }
858              
859             sub __real_inline_generator {
860 24     24   81 my ($is_perl_safe, $digits, $_NumRange_param, $no_naninf) = @_;
861 24         71 my $_BlessedFloat_param = $_BlessedFloat->parameterize($digits);
862              
863 24 100       1522 if ($no_naninf) {
864             return $is_perl_safe ?
865             sub { (
866 222     222   901073 $_PerlNum->inline_check($_[1]).' || '.$_BlessedFloat_param->inline_check($_[1]),
867             $_NumRange_param->inline_check($_[1])
868             ) } :
869 78     78   300759 sub { ($_BlessedFloat_param->inline_check($_[1]), $_NumRange_param->inline_check($_[1])) }
870 12 100       101 ;
871             }
872             else {
873             return $is_perl_safe ?
874             sub { (
875 222     222   1313692 $_PerlNum->inline_check($_[1]).' || '.$_BlessedFloat_param->inline_check($_[1]),
876             $_NumRange_param->inline_check($_[1]).' || '.$_NaNInf->inline_check($_[1])
877             ) } :
878             sub { (
879 78     78   712320 $_BlessedFloat_param->inline_check($_[1]),
880             $_NumRange_param->inline_check($_[1]).' || '.$_NaNInf->inline_check($_[1])
881             ) }
882 12 100       133 ;
883             }
884             }
885              
886             $meta->add_type(
887             name => 'FloatBinary',
888             parent => $_FloatSafeNum,
889             library => __PACKAGE__,
890             # kinda pointless without the parameters
891             constraint_generator => sub {
892             my $self = $Type::Tiny::parameterize_type;
893             my ($bits, $ebits) = (shift, shift);
894             $bits =~ /\A[0-9]+\z/ or _croak( "First parameter to FloatBinary[`b, `e] expected to be a positive integer; got $bits");
895             $ebits =~ /\A[0-9]+\z/ or _croak("Second parameter to FloatBinary[`b, `e] expected to be a positive integer; got $ebits");
896              
897             my $sbits = $bits - 1 - $ebits; # remove sign bit and exponent bits = significand precision
898              
899             # MAX = (2 - 2**(-$sbits-1)) * 2**($ebits-1)
900             my $emax = $bigtwo->copy->bpow($ebits-1)->bsub(1); # Y = (2**($ebits-1)-1)
901             my $smin = $bigtwo->copy->bpow(-$sbits-1)->bmul(-1)->badd(2); # Z = (2 - X) = -X + 2 (where X = 2**(-$sbits-1) )
902             my $max = $bigtwo->copy->bpow($emax)->bmul($smin); # MAX = 2**Y * Z
903              
904             my $digits = ceil( $sbits * _BASE2_LOG );
905              
906             my $is_perl_safe = (
907             Data::Float::significand_bits >= $sbits &&
908             Data::Float::max_finite_exp >= 2 ** $ebits - 1 &&
909             Data::Float::have_infinite &&
910             Data::Float::have_nan
911             );
912              
913             my $_NumRange_param = $_NumRange->parameterize(-$max, $max);
914              
915             Type::Tiny->new(
916             display_name => "FloatBinary[$bits, $ebits]",
917             parent => $self,
918             library => __PACKAGE__,
919             constraint => __real_constraint_generator($is_perl_safe, $digits, $_NumRange_param),
920             inlined => __real_inline_generator ($is_perl_safe, $digits, $_NumRange_param),
921             );
922             },
923             );
924              
925             #pod =head3 FloatDecimal[`d, `e]
926             #pod
927             #pod A floating-point number that can hold a C<`d> digit number with C<`e> digits of exponent. Modeled
928             #pod after the IEEE 754 "decimal" float. Rejects all Perl NVs that won't support the dimensions. (See
929             #pod L.)
930             #pod
931             #pod =cut
932              
933             $meta->add_type(
934             name => 'FloatDecimal',
935             parent => $_FloatSafeNum,
936             library => __PACKAGE__,
937             # kinda pointless without the parameters
938             constraint_generator => sub {
939             my $self = $Type::Tiny::parameterize_type;
940             my ($digits, $emax) = (shift, shift);
941             $digits =~ /\A[0-9]+\z/ or _croak( "First parameter to FloatDecimal[`d, `e] expected to be a positive integer; got $digits");
942             $emax =~ /\A[0-9]+\z/ or _croak("Second parameter to FloatDecimal[`d, `e] expected to be a positive integer; got $emax");
943              
944             # We're not going to worry about the (extreme) edge case that
945             # Perl might be compiled with decimal float NVs, but we still
946             # need to convert to base-2.
947             my $sbits = ceil( $digits / _BASE2_LOG );
948             my $emax2 = ceil( $emax / _BASE2_LOG );
949              
950             my $max = $bigten->copy->bpow($emax)->bmul( '9.'.('9' x ($digits-1)) );
951              
952             my $is_perl_safe = (
953             Data::Float::significand_bits >= $sbits &&
954             Data::Float::max_finite_exp >= $emax2 &&
955             Data::Float::have_infinite &&
956             Data::Float::have_nan
957             );
958              
959             my $_NumRange_param = $_NumRange->parameterize(-$max, $max);
960              
961             Type::Tiny->new(
962             display_name => "FloatDecimal[$digits, $emax]",
963             parent => $self,
964             library => __PACKAGE__,
965             constraint => __real_constraint_generator($is_perl_safe, $digits, $_NumRange_param),
966             inlined => __real_inline_generator ($is_perl_safe, $digits, $_NumRange_param),
967             );
968             },
969             );
970              
971             #pod =head2 Fixed-point numbers
972             #pod
973             #pod =head3 RealSafeNum
974             #pod
975             #pod Like L, but rejects any NaN/Inf.
976             #pod
977             #pod =cut
978              
979             my $_RealSafeNum = $meta->add_type( Type::Tiny::Intersection->new(
980             library => __PACKAGE__,
981             type_constraints => [ $_RealNum, $_FloatSafeNum ],
982             )->create_child_type(
983             name => 'RealSafeNum',
984             library => __PACKAGE__,
985             inlined => sub {
986             my ($self, $val) = @_;
987             return (
988             $_NumLike->inline_check($val),
989             "( !ref($val) && ".$_NumRange_perlsafe->inline_check($val)." && not (".
990             "Data::Float::float_is_nan($val) || Data::Float::float_is_infinite($val))".
991             ') || ('.
992             Types::Standard::Object->inline_check($val)." && Scalar::Util::blessed($val)->new(1.2) == 1.2 && ".
993             "not ($val->can('is_nan') && $val->is_nan || $val->can('is_inf') && ($val->is_inf('+') || $val->is_inf('-')) )".
994             ')'
995             );
996             },
997             ) );
998              
999             #pod =head3 FixedBinary[`b, `s]
1000             #pod
1001             #pod A fixed-point number, represented as a C<`b> bit integer than has been shifted by C<`s> digits. For example, a
1002             #pod C has a max of C<2**31-1 / 10**4 = 214748.3647>. Because integers do not hold NaN/Inf, this type fails
1003             #pod on those.
1004             #pod
1005             #pod Otherwise, it has the same properties and caveats as the parameterized C types.
1006             #pod
1007             #pod =cut
1008              
1009             $meta->add_type(
1010             name => 'FixedBinary',
1011             parent => $_RealSafeNum,
1012             library => __PACKAGE__,
1013             # kinda pointless without the parameters
1014             constraint_generator => sub {
1015             my $self = $Type::Tiny::parameterize_type;
1016             my ($bits, $scale) = (shift, shift);
1017             $bits =~ /\A[0-9]+\z/ or _croak( "First parameter to FixedBinary[`b, `s] expected to be a positive integer; got $bits");
1018             $scale =~ /\A[0-9]+\z/ or _croak("Second parameter to FixedBinary[`b, `s] expected to be a positive integer; got $scale");
1019              
1020             my $sbits = $bits - 1;
1021              
1022             # So, we have a base-10 scale and a base-2 set of $bits. Lovely.
1023             # We can't actually figure out if it's Perl safe until we find the
1024             # $max, adjust with the $scale, and then go BACK to base-2 limits.
1025             my $div = $bigten->copy->bpow($scale);
1026             my ($neg, $pos) = (
1027             # bdiv returns (quo,rem) in list context :/
1028             scalar $bigtwo->copy->bpow($sbits)->bmul(-1)->bdiv($div),
1029             scalar $bigtwo->copy->bpow($sbits)->bsub(1)->bdiv($div),
1030             );
1031              
1032             my $digits = ceil( $sbits * _BASE2_LOG );
1033             my $emin2 = ceil( $scale / _BASE2_LOG );
1034              
1035             my $is_perl_safe = (
1036             Data::Float::significand_bits >= $sbits &&
1037             Data::Float::min_finite_exp <= -$emin2
1038             );
1039              
1040             my $_NumRange_param = $_NumRange->parameterize($neg, $pos);
1041              
1042             Type::Tiny->new(
1043             display_name => "FixedBinary[$bits, $scale]",
1044             parent => $self,
1045             library => __PACKAGE__,
1046             constraint => __real_constraint_generator($is_perl_safe, $digits, $_NumRange_param, 1),
1047             inlined => __real_inline_generator ($is_perl_safe, $digits, $_NumRange_param, 1),
1048             );
1049             },
1050             );
1051              
1052             #pod =head3 FixedDecimal[`d, `s]
1053             #pod
1054             #pod Like L, but for a C<`d> digit integer. Or, you could think of C<`d> and C<`s> as accuracy (significant
1055             #pod figures) and decimal precision, respectively.
1056             #pod
1057             #pod =cut
1058              
1059             $meta->add_type(
1060             name => 'FixedDecimal',
1061             parent => $_RealSafeNum,
1062             library => __PACKAGE__,
1063             # kinda pointless without the parameters
1064             constraint_generator => sub {
1065             my $self = $Type::Tiny::parameterize_type;
1066             my ($digits, $scale) = (shift, shift);
1067             $digits =~ /\A[0-9]+\z/ or _croak( "First parameter to FixedDecimal[`d, `s] expected to be a positive integer; got $digits");
1068             $scale =~ /\A[0-9]+\z/ or _croak("Second parameter to FixedDecimal[`d, `s] expected to be a positive integer; got $scale");
1069              
1070             my $sbits = ceil( $digits / _BASE2_LOG );
1071             my $emin2 = ceil( $scale / _BASE2_LOG );
1072              
1073             my $is_perl_safe = (
1074             Data::Float::significand_bits >= $sbits &&
1075             Data::Float::min_finite_exp <= -$emin2
1076             );
1077              
1078             my $div = $bigten->copy->bpow($scale);
1079             my $max = $bigten->copy->bpow($digits)->bsub(1)->bdiv($div);
1080              
1081             my $_NumRange_param = $_NumRange->parameterize(-$max, $max);
1082              
1083             Type::Tiny->new(
1084             display_name => "FixedDecimal[$digits, $scale]",
1085             parent => $self,
1086             library => __PACKAGE__,
1087             constraint => __real_constraint_generator($is_perl_safe, $digits, $_NumRange_param, 1),
1088             inlined => __real_inline_generator ($is_perl_safe, $digits, $_NumRange_param, 1),
1089             );
1090             },
1091             );
1092              
1093             #############################################################################
1094             # Character types
1095              
1096             #pod =head2 Characters
1097             #pod
1098             #pod Characters are basically encoded numbers, so there's a few types here. If you need types that handle multi-length strings, you're
1099             #pod better off using L.
1100             #pod
1101             #pod =head3 Char
1102             #pod
1103             #pod A single character. Unicode is supported, but it must be decoded first. A multi-byte character that Perl thinks is two separate
1104             #pod characters will fail this type.
1105             #pod
1106             #pod =head3 Char[`b]
1107             #pod
1108             #pod A single character that fits within C<`b> bits. Unicode is supported, but it must be decoded first.
1109             #pod
1110             #pod =cut
1111              
1112             $meta->add_type(
1113             name => 'Char',
1114             parent => Types::Standard::Str,
1115             library => __PACKAGE__,
1116             constraint => sub { length($_) == 1 }, # length() will do a proper Unicode char length
1117             inlined => sub {
1118             my ($self, $val) = @_;
1119             undef, "length($val) == 1";
1120             },
1121             constraint_generator => sub {
1122             my $self = $Type::Tiny::parameterize_type;
1123             my ($bits) = (shift);
1124             $bits =~ /\A[0-9]+\z/ or _croak("Parameter to Char[`b] expected to be a positive integer; got $bits");
1125              
1126             Type::Tiny->new(
1127             display_name => "Char[$bits]",
1128             parent => $self,
1129             library => __PACKAGE__,
1130             constraint => sub { ord($_) < 2**$bits },
1131             inlined => sub {
1132             my $val = $_[1];
1133             (undef, "ord($val) < 2**$bits");
1134             },
1135             );
1136             },
1137             );
1138              
1139             #############################################################################
1140             # Types from Types::Common::Numeric
1141              
1142             #pod =head2 Types::Common::Numeric analogues
1143             #pod
1144             #pod The L module has a lot of useful types, but none of them are compatible with blessed numbers. This module
1145             #pod re-implements them to be grandchildren of L and L, which allows blessed numbers.
1146             #pod
1147             #pod Furthermore, the L and L checks are already implemented and described above.
1148             #pod
1149             #pod =head3 PositiveNum
1150             #pod
1151             #pod Accepts non-zero numbers in the positive range.
1152             #pod
1153             #pod =cut
1154              
1155             $meta->add_type(
1156             name => 'PositiveNum',
1157             parent => $_NumRange->parameterize(0, undef, 1),
1158             message => sub { "Must be a positive number" },
1159             );
1160              
1161             #pod =head3 PositiveOrZeroNum
1162             #pod
1163             #pod Accepts numbers in the positive range, or zero.
1164             #pod
1165             #pod =cut
1166              
1167             $meta->add_type(
1168             name => 'PositiveOrZeroNum',
1169             parent => $_NumRange->parameterize(0),
1170             message => sub { "Must be a number greater than or equal to zero" },
1171             );
1172              
1173             #pod =head3 PositiveInt
1174             #pod
1175             #pod Accepts non-zero integers in the positive range.
1176             #pod
1177             #pod =cut
1178              
1179             $meta->add_type(
1180             name => 'PositiveInt',
1181             parent => $_IntRange->parameterize(1),
1182             message => sub { "Must be a positive integer" },
1183             );
1184              
1185             #pod =head3 PositiveOrZeroInt
1186             #pod
1187             #pod Accepts integers in the positive range, or zero.
1188             #pod
1189             #pod =cut
1190              
1191             $meta->add_type(
1192             name => 'PositiveOrZeroInt',
1193             parent => $_IntRange->parameterize(0),
1194             message => sub { "Must be an integer greater than or equal to zero" },
1195             );
1196              
1197             #pod =head3 NegativeNum
1198             #pod
1199             #pod Accepts non-zero numbers in the negative range.
1200             #pod
1201             #pod =cut
1202              
1203             $meta->add_type(
1204             name => 'NegativeNum',
1205             parent => $_NumRange->parameterize(undef, 0, undef, 1),
1206             message => sub { "Must be a negative number" },
1207             );
1208              
1209             #pod =head3 NegativeOrZeroNum
1210             #pod
1211             #pod Accepts numbers in the negative range, or zero.
1212             #pod
1213             #pod =cut
1214              
1215             $meta->add_type(
1216             name => 'NegativeOrZeroNum',
1217             parent => $_NumRange->parameterize(undef, 0),
1218             message => sub { "Must be a number less than or equal to zero" },
1219             );
1220              
1221             #pod =head3 NegativeInt
1222             #pod
1223             #pod Accepts non-zero integers in the negative range.
1224             #pod
1225             #pod =cut
1226              
1227             $meta->add_type(
1228             name => 'NegativeInt',
1229             parent => $_IntRange->parameterize(undef, -1),
1230             message => sub { "Must be a negative integer" },
1231             );
1232              
1233             #pod =head3 NegativeOrZeroInt
1234             #pod
1235             #pod Accepts integers in the negative range, or zero.
1236             #pod
1237             #pod =cut
1238              
1239             $meta->add_type(
1240             name => 'NegativeOrZeroInt',
1241             parent => $_IntRange->parameterize(undef, 0),
1242             message => sub { "Must be an integer less than or equal to zero" },
1243             );
1244              
1245             #pod =head3 SingleDigit
1246             #pod
1247             #pod Accepts integers between -9 and 9.
1248             #pod
1249             #pod =cut
1250              
1251             $meta->add_type(
1252             name => 'SingleDigit',
1253             parent => $_IntRange->parameterize(-9, 9),
1254             message => sub { "Must be a single digit" },
1255             );
1256              
1257             42;
1258              
1259             __END__