File Coverage

blib/lib/Types/Numbers.pm
Criterion Covered Total %
statement 65 71 91.5
branch 14 22 63.6
condition 0 12 0.0
subroutine 22 27 81.4
pod n/a
total 101 132 76.5


line stmt bran cond sub pod time code
1             package Types::Numbers;
2              
3             our $AUTHORITY = 'cpan:BBYRD'; # AUTHORITY
4             our $VERSION = '0.94'; # VERSION
5             # ABSTRACT: Type constraints for numbers
6              
7             #############################################################################
8             # Modules
9              
10 5     5   96344 use v5.8.8;
  5         14  
  5         170  
11 5     5   17 use strict;
  5         6  
  5         120  
12 5     5   17 use warnings;
  5         6  
  5         203  
13              
14             our @EXPORT_OK = ();
15              
16 5     5   2277 use Type::Library -base;
  5         91890  
  5         41  
17 5     5   3323 use Type::Tiny::Intersection;
  5         4889  
  5         175  
18 5     5   2107 use Type::Tiny::Union;
  5         5815  
  5         128  
19 5     5   2690 use Types::Standard v0.030 (); # support for Error::TypeTiny
  5         177666  
  5         244  
20              
21 5     5   38 use Scalar::Util 1.20 (qw(blessed looks_like_number)); # support for overloaded/blessed looks_like_number
  5         118  
  5         304  
22 5     5   2901 use POSIX 'ceil';
  5         23283  
  5         24  
23 5     5   8672 use Math::BigInt 1.92; # somewhat a stab in the dark for a passable version
  5         81542  
  5         27  
24 5     5   61600 use Math::BigFloat 1.65; # earliest version that passes tests
  5         78531  
  5         26  
25 5     5   5850 use Data::Float;
  5         32332  
  5         269  
26 5     5   2687 use Data::Integer;
  5         30932  
  5         344  
27              
28             use constant {
29 5         414 _BASE2_LOG => log(2) / log(10),
30 5     5   29 };
  5         7  
31              
32 0     0   0 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  0         0  
33              
34 5     5   25 no warnings; # don't warn on type checks
  5         5  
  5         23023  
35              
36             #############################################################################
37             # Basic globals
38              
39             my $bigtwo = Math::BigFloat->new(2);
40             my $bigten = Math::BigFloat->new(10);
41              
42             # Large 64-bit floats (long doubles) tend to stringify themselves in exponent notation, even
43             # though the number is still pristine. IOW, the numeric form is perfect, but the string form
44             # loses information. This can be a problem for stringified inlines.
45             my @df_max_int_parts = Data::Float::float_parts( Data::Float::max_integer );
46             my $DF_MAX_INT = $bigtwo->copy->bpow($df_max_int_parts[1])->bmul($df_max_int_parts[2])->as_int;
47              
48             my $SAFE_NUM_MIN = Math::BigInt->new(
49             Data::Integer::min_signed_natint < $DF_MAX_INT * -1 ?
50             Data::Integer::min_signed_natint : $DF_MAX_INT * -1
51             );
52             my $SAFE_NUM_MAX = Math::BigInt->new(
53             Data::Integer::max_unsigned_natint > $DF_MAX_INT * 1 ?
54             Data::Integer::max_unsigned_natint : $DF_MAX_INT * 1,
55             );
56              
57             my $meta = __PACKAGE__->meta;
58              
59             #############################################################################
60             # Framework types
61              
62             ### TODO: Coercions where safe ###
63              
64             # Moose and Type::Tiny types both don't seem to support Math::Big* = Num.
65             # So, we have to start almost from stratch.
66             my $_NumLike = $meta->add_type(
67             name => 'NumLike',
68             parent => Types::Standard::Defined,
69             library => __PACKAGE__,
70             constraint => sub { looks_like_number $_ },
71             inlined => sub { "Scalar::Util::looks_like_number($_[1])" },
72             );
73              
74             my $_NumRange = $meta->add_type(
75             name => 'NumRange',
76             parent => $_NumLike,
77             library => __PACKAGE__,
78             # kinda pointless without the parameters
79             constraint_generator => sub {
80             my $self = $Type::Tiny::parameterize_type;
81             my ($min, $max) = (shift, shift);
82             looks_like_number($min) or _croak( "First parameter to NumRange[`n, `p] expected to be a number; got $min");
83             looks_like_number($max) or _croak("Second parameter to NumRange[`n, `p] expected to be a number; got $max");
84              
85             my ($Imin, $Imax) = ($min, $max);
86             $Imin = blessed($min)."\->new('$min')" if (blessed $min);
87             $Imax = blessed($max)."\->new('$max')" if (blessed $max);
88              
89             Type::Tiny->new(
90             display_name => "NumRange[$min, $max]",
91             parent => $self,
92             library => __PACKAGE__,
93             constraint => sub {
94             my $val = $_;
95             $val >= $min && $val <= $max;
96             },
97             inlined => sub {
98             my ($self, $val) = @_;
99             $self->parent->inline_check($val)." && $val >= $Imin && $val <= $Imax";
100             },
101             );
102             },
103             );
104              
105             # we need to optimize out all of the NumLike checks
106             my $_NumRange_perlsafe = Type::Tiny->new(
107             display_name => "_NumRange_perlsafe",
108             parent => $_NumLike,
109             # no equals because MAX+1 = MAX after truncation
110             constraint => sub { $_ > $SAFE_NUM_MIN && $_ < $SAFE_NUM_MAX },
111             inlined => sub { "$_[1] > ".$SAFE_NUM_MIN." && $_[1] < ".$SAFE_NUM_MAX },
112             );
113              
114             ### XXX: This string equality check is necessary because Math::BigInt seems to think 1.5 == 1.
115             ### However, this is problematic with long doubles that stringify into E notation.
116             my $_IntLike = $meta->add_type(
117             name => 'IntLike',
118             parent => $_NumLike,
119             library => __PACKAGE__,
120             constraint => sub { /\d+/ && int($_) == $_ && (int($_) eq $_ || !ref($_)) },
121             inlined => sub {
122             my ($self, $val) = @_;
123             $self->parent->inline_check($val)." && $val =~ /\\d+/ && int($val) == $val && (int($val) eq $val || !ref($val))";
124             },
125             );
126              
127             # This is basically LaxNum with a different parent
128             my $_PerlNum = $meta->add_type(
129             name => 'PerlNum',
130             parent => $_NumLike,
131             library => __PACKAGE__,
132             constraint => Types::Standard::LaxNum->constraint,
133             inlined => Types::Standard::LaxNum->inlined,
134             );
135              
136             my $_BlessedNum = $meta->add_type( Type::Tiny::Intersection->new(
137             name => 'BlessedNum',
138             display_name => 'BlessedNum',
139             library => __PACKAGE__,
140             type_constraints => [ $_NumLike, Types::Standard::Object ],
141             constraint_generator => sub {
142             my $self = $Type::Tiny::parameterize_type;
143             my $digits = shift;
144             $digits =~ /\A[0-9]+\z/ or _croak("Parameter to BlessedNum[`d] expected to be a positive integer; got $digits");
145              
146             my $parent = $self;
147             Type::Tiny->new(
148             display_name => "BlessedNum[$digits]",
149             parent => $self,
150             library => __PACKAGE__,
151             constraint => sub {
152             my $val = $_;
153              
154             $val->can('accuracy') && $val->accuracy >= $digits ||
155             $val->can('div_scale') && $val->div_scale >= $digits;
156             },
157             inlined => sub {
158             my ($self, $val) = @_;
159              
160             $parent->inline_check($val).' && ( '.
161             "$val->can('accuracy') && $val->accuracy && $val->accuracy >= $digits || ".
162             "$val->can('div_scale') && $val->div_scale && $val->div_scale >= $digits ".
163             ')';
164             },
165             );
166             },
167             ) );
168              
169             my $_NaN = $meta->add_type(
170             name => 'NaN',
171             parent => $_NumLike,
172             library => __PACKAGE__,
173             constraint => sub {
174             my $val = $_;
175              
176             Types::Standard::Object->check($val) && $val->can('is_nan') && $val->is_nan ||
177             Data::Float::float_is_nan($val);
178             },
179             inlined => sub {
180             my ($self, $val) = @_;
181             $self->parent->inline_check($val).' && ('.
182             ' '.Types::Standard::Object->inline_check($val)." && $val->can('is_nan') && $val->is_nan ||".
183             ' '."Data::Float::float_is_nan($val)".
184             ')';
185             },
186             );
187              
188             my $_Inf = $meta->add_type(
189             name => 'Inf',
190             parent => $_NumLike,
191             library => __PACKAGE__,
192             constraint => sub {
193             my $val = $_;
194              
195             Types::Standard::Object->check($val) && $val->can('is_inf') && ($val->is_inf('+') || $val->is_inf('-')) ||
196             Data::Float::float_is_infinite($val);
197             },
198             inlined => sub {
199             my ($self, $val) = @_;
200             $self->parent->inline_check($val).' && ('.
201             ' '.Types::Standard::Object->inline_check($val)." && $val->can('is_inf') && ($val->is_inf('+') || $val->is_inf('-')) ||".
202             ' '."Data::Float::float_is_infinite($val)".
203             ')';
204             },
205             constraint_generator => sub {
206             my $self = $Type::Tiny::parameterize_type;
207             my $sign = shift;
208             $sign =~ /\A[+\-]\z/ or _croak("Parameter to Inf[`s] expected to be a plus or minus sign; got $sign");
209              
210             Type::Tiny->new(
211             display_name => "Inf[$sign]",
212             parent => $self,
213             library => __PACKAGE__,
214             constraint => sub {
215             my $val = $_;
216              
217             Types::Standard::Object->check($val) && $val->can('is_inf') && $val->is_inf($sign) ||
218             Data::Float::float_is_infinite($val) && Data::Float::float_sign($val) eq $sign;
219             },
220             inlined => sub {
221             my ($self, $val) = @_;
222              
223             $self->parent->inline_check($val).' && ( '.
224             Types::Standard::Object->inline_check($val)." && $val->can('is_inf') && $val->is_inf('$sign') || ".
225             "Data::Float::float_is_infinite($val) && Data::Float::float_sign($val) eq '$sign' ".
226             ')';
227             },
228             );
229             },
230             );
231              
232             # this is used a lot for floats, but we need to optimize out all of the NumLike checks
233             my $_NaNInf = Type::Tiny::Union->new(
234             type_constraints => [ $_NaN, $_Inf ],
235             )->create_child_type(
236             name => 'NaNInf',
237             constraint => sub {
238             # looks_like_number($_) &&
239             Types::Standard::Object->check($_) && (
240             $_->can('is_nan') && $_->is_nan ||
241             $_->can('is_inf') && ($_->is_inf('+') || $_->is_inf('-'))
242             ) || Data::Float::float_is_nan($_) || Data::Float::float_is_infinite($_)
243             },
244             inlined => sub {
245             my ($self, $val) = @_;
246             # looks_like_number($val) &&
247             Types::Standard::Object->inline_check($val)." && ( ".
248             "$val->can('is_nan') && $val->is_nan || ".
249             "$val->can('is_inf') && ($val->is_inf('+') || $val->is_inf('-')) ".
250             ") || Data::Float::float_is_nan($val) || Data::Float::float_is_infinite($val)";
251             },
252             );
253              
254             my $_not_NaNInf = $_NaNInf->complementary_type;
255              
256             my $_RealNum = $meta->add_type( Type::Tiny::Intersection->new(
257             name => 'RealNum',
258             display_name => 'RealNum',
259             library => __PACKAGE__,
260             type_constraints => [ $_NumLike, $_not_NaNInf ],
261             ) );
262              
263             #############################################################################
264             # Integer types
265              
266             # Helper subs
267             sub __integer_bits_vars {
268 12     12   17 my ($bits, $is_unsigned) = @_;
269              
270 12         20 my $sbits = $bits - 1;
271              
272 12         48 my ($neg, $spos, $upos) = (
273             $bigtwo->copy->bpow($sbits)->bmul(-1),
274             $bigtwo->copy->bpow($sbits)->bsub(1),
275             $bigtwo->copy->bpow( $bits)->bsub(1),
276             );
277 12         20107 my $sdigits = ceil( $sbits * _BASE2_LOG );
278 12         24 my $udigits = ceil( $bits * _BASE2_LOG );
279              
280 12 100       62 return $is_unsigned ?
281             (0, $upos, $udigits) :
282             ($neg, $spos, $sdigits)
283             ;
284             }
285              
286             my $_PerlSafeInt = $meta->add_type( Type::Tiny::Intersection->new(
287             library => __PACKAGE__,
288             type_constraints => [ $_PerlNum, $_IntLike, $_NumRange_perlsafe ],
289             )->create_child_type(
290             name => 'PerlSafeInt',
291             library => __PACKAGE__,
292             inlined => sub {
293             my $val = $_[1];
294             "defined $val && !ref($val) && $val =~ /\\d+/ && int($val) == $val && ".$_NumRange_perlsafe->inline_check($val);
295             },
296             ) );
297              
298             my $_BlessedInt = $meta->add_type( Type::Tiny::Intersection->new(
299             library => __PACKAGE__,
300             type_constraints => [ $_BlessedNum, $_IntLike ],
301             )->create_child_type(
302             name => 'BlessedInt',
303             library => __PACKAGE__,
304             inlined => sub {
305             my $val = $_[1];
306             Types::Standard::Object->inline_check($val)." && $val =~ /\\d+/ && int($val) == $val && int($val) eq $val";
307             },
308             constraint_generator => sub {
309             my $self = $Type::Tiny::parameterize_type;
310             my $digits = shift;
311             $digits =~ /\A[0-9]+\z/ or _croak("Parameter to BlessedInt[`d] expected to be a positive integer; got $digits");
312              
313             my $_BlessedNum_param = $_BlessedNum->parameterize($digits);
314              
315             Type::Tiny->new(
316             display_name => "BlessedInt[$digits]",
317             parent => $self,
318             library => __PACKAGE__,
319             constraint => sub {
320             $_IntLike->check($_) && $_BlessedNum_param->check($_) && do {
321             my $num = $_;
322             $num =~ s/\D+//g;
323             length($num) <= $digits
324             }
325             },
326             inlined => sub {
327             my $val = $_[1];
328             $_BlessedNum_param->inline_check($val).' && '.
329             "$val =~ /\\d+/ && int($val) == $val && int($val) eq $val && do { ".
330             'my $num = '.$val.'; '.
331             '$num =~ s/\D+//g; '.
332             'length($num) <= '.$digits.' '.
333             '}';
334             },
335             );
336             },
337             ) );
338              
339             $meta->add_type( Type::Tiny::Union->new(
340             #parent => $_IntLike,
341             library => __PACKAGE__,
342             type_constraints => [ $_PerlSafeInt, $_BlessedInt ],
343             )->create_child_type(
344             name => 'SignedInt',
345             library => __PACKAGE__,
346             inlined => sub {
347             my $val = $_[1];
348             $_IntLike->inline_check($val).' && ('.
349             $_NumRange_perlsafe->inline_check($val).' || '.Types::Standard::Object->inline_check($val).
350             ')';
351             },
352             constraint_generator => sub {
353             my $self = $Type::Tiny::parameterize_type;
354             my $bits = shift;
355             $bits =~ /\A[0-9]+\z/ or _croak("Parameter to SignedInt[`b] expected to be a positive integer; got $bits");
356              
357             my ($min, $max, $digits) = __integer_bits_vars($bits, 0);
358             my $_BlessedInt_param = $_BlessedInt->parameterize($digits);
359             my $_NumRange_param = $_NumRange ->parameterize($min, $max);
360              
361             Type::Tiny::Intersection->new(
362             library => __PACKAGE__,
363             type_constraints => [ $self, ($_PerlSafeInt|$_BlessedInt_param), $_NumRange_param ],
364             )->create_child_type(
365             display_name => "SignedInt[$bits]",
366             inlined => sub {
367             my $val = $_[1];
368             '('.$_PerlSafeInt->inline_check($val).' || '.$_BlessedInt_param->inline_check($val).') && '.
369             $_NumRange_param->inline_check($val);
370             },
371             );
372             },
373             ) );
374              
375             $meta->add_type(
376             name => 'UnsignedInt',
377             parent => $_IntLike,
378             library => __PACKAGE__,
379             constraint => sub { $_IntLike->check($_) && $_ >= 0 && ($_PerlSafeInt->check($_) || $_BlessedNum->check($_)) },
380             inlined => sub {
381             my $val = $_[1];
382             $_IntLike->inline_check($val)." && $val >= 0 && (".
383             $_NumRange_perlsafe->inline_check($val).' || '.Types::Standard::Object->inline_check($val).
384             ')';
385             },
386             constraint_generator => sub {
387             my $self = $Type::Tiny::parameterize_type;
388             my $bits = shift;
389             $bits =~ /\A[0-9]+\z/ or _croak("Parameter to UnsignedInt[`b] expected to be a positive integer; got $bits");
390              
391             my ($min, $max, $digits) = __integer_bits_vars($bits, 1);
392             my $_BlessedNum_param = $_BlessedNum->parameterize($digits); # IntLike check extracted out
393             my $_NumRange_param = $_NumRange ->parameterize($min, $max);
394              
395             # inline will already have the IntLike check, and maybe not need the extra NumRange check
396             my $perlsafe_inline = $min >= $SAFE_NUM_MIN && $max <= $SAFE_NUM_MAX ?
397             sub { Types::Standard::Str->inline_check($_[0]) } :
398             sub { '('.Types::Standard::Str->inline_check($_[0]).' && '.$_NumRange_perlsafe->inline_check($_[0]).')' }
399             ;
400              
401             Type::Tiny->new(
402             display_name => "UnsignedInt[$bits]",
403             parent => $self,
404             library => __PACKAGE__,
405             constraint => sub {
406             $_IntLike->check($_) && $_NumRange_param->check($_) &&
407             ($_PerlSafeInt->check($_) || $_BlessedNum_param->check($_));
408             },
409             inlined => sub {
410             my $val = $_[1];
411             $_IntLike->inline_check($val).' && '.$_NumRange_param->inline_check($val).' && '.
412             '('.$perlsafe_inline->($val).' || '.$_BlessedNum_param->inline_check($val).')';
413             },
414             );
415             },
416             );
417              
418             #############################################################################
419             # Float/fixed types
420              
421             my $_BlessedFloat = $meta->add_type(
422             name => 'BlessedFloat',
423             parent => $_BlessedNum,
424             library => __PACKAGE__,
425             constraint => sub { blessed($_)->new(1.2) == 1.2 },
426             inlined => sub {
427             my ($self, $val) = @_;
428             $self->parent->inline_check($val)." && Scalar::Util::blessed($val)\->new(1.2) == 1.2";
429             },
430             constraint_generator => sub {
431             my $self = $Type::Tiny::parameterize_type;
432             my $digits = shift;
433             $digits =~ /\A[0-9]+\z/ or _croak("Parameter to BlessedFloat[`d] expected to be a positive integer; got $digits");
434              
435             my $_BlessedNum_param = $_BlessedNum->parameterize($digits);
436              
437             Type::Tiny->new(
438             display_name => "BlessedFloat[$digits]",
439             parent => $self,
440             library => __PACKAGE__,
441             constraint => sub { $_BlessedNum_param->check($_) && blessed($_)->new(1.2) == 1.2 },
442             inlined => sub {
443             my ($self, $val) = @_;
444             $_BlessedNum_param->inline_check($val)." && Scalar::Util::blessed($val)\->new(1.2) == 1.2";
445             },
446             );
447             },
448             );
449              
450             my $_PerlSafeFloat = $meta->add_type(
451             name => 'PerlSafeFloat',
452             parent => $_PerlNum,
453             library => __PACKAGE__,
454             constraint => sub { $_NumRange_perlsafe->check($_) || Data::Float::float_is_nan($_) || Data::Float::float_is_infinite($_) },
455             inlined => sub {
456             my ($self, $val) = @_;
457             $self->parent->inline_check($val).' && ('.
458             $_NumRange_perlsafe->inline_check($val)." || Data::Float::float_is_nan($val) || Data::Float::float_is_infinite($val)".
459             ')';
460             },
461             );
462              
463             my $_FloatSafeNum = $meta->add_type( Type::Tiny::Union->new(
464             library => __PACKAGE__,
465             type_constraints => [ $_PerlSafeFloat, $_BlessedFloat ],
466             )->create_child_type(
467             name => 'FloatSafeNum',
468             library => __PACKAGE__,
469             inlined => sub {
470             my ($self, $val) = @_;
471             $self->parent->inline_check($val).' && ('.
472             "!ref($val) && (".
473             $_NumRange_perlsafe->inline_check($val)." || Data::Float::float_is_nan($val) || Data::Float::float_is_infinite($val)".
474             ') || '.
475             Types::Standard::Object->inline_check($val)." && Scalar::Util::blessed($val)->new(1.2) == 1.2".
476             ')';
477             },
478             ) );
479              
480             my $_RealSafeNum = $meta->add_type( Type::Tiny::Intersection->new(
481             library => __PACKAGE__,
482             type_constraints => [ $_RealNum, $_FloatSafeNum ],
483             )->create_child_type(
484             name => 'RealSafeNum',
485             library => __PACKAGE__,
486             inlined => sub {
487             my ($self, $val) = @_;
488             $_NumLike->inline_check($val).' && ('.
489             "( !ref($val) && ".$_NumRange_perlsafe->inline_check($val)." && not (".
490             "Data::Float::float_is_nan($val) || Data::Float::float_is_infinite($val))".
491             ') || ('.
492             Types::Standard::Object->inline_check($val)." && Scalar::Util::blessed($val)->new(1.2) == 1.2 && ".
493             "not ($val->can('is_nan') && $val->is_nan || $val->can('is_inf') && ($val->is_inf('+') || $val->is_inf('-')) )".
494             ')'.
495             ')';
496             },
497             ) );
498              
499             ### NOTE: These two are very close to another type, but there's just too many variables
500             ### to throw into a typical type
501              
502             sub __real_constraint_generator {
503 24     24   46 my ($is_perl_safe, $digits, $_NumRange_param, $no_naninf) = @_;
504 24         60 my $_BlessedFloat_param = $_BlessedFloat->parameterize($digits);
505              
506 24 100       7601 if ($no_naninf) {
507             return $is_perl_safe ?
508 0 0 0 0   0 sub { ( $_PerlNum->check($_) || $_BlessedFloat_param->check($_) ) && $_NumRange_param->check($_) } :
509 0 0   0   0 sub { $_BlessedFloat_param->check($_) && $_NumRange_param->check($_) }
510 12 100       67 ;
511             }
512             else {
513             return $is_perl_safe ?
514 0 0 0 0   0 sub { ( $_PerlNum->check($_) || $_BlessedFloat_param->check($_) ) && $_NumRange_param->check($_) || $_NaNInf->check($_) } :
      0        
515 0 0 0 0   0 sub { $_BlessedFloat_param->check($_) && ( $_NumRange_param->check($_) || $_NaNInf->check($_) ); }
516 12 100       76 ;
517             }
518             }
519              
520             sub __real_inline_generator {
521 24     24   37 my ($is_perl_safe, $digits, $_NumRange_param, $no_naninf) = @_;
522 24         53 my $_BlessedFloat_param = $_BlessedFloat->parameterize($digits);
523              
524 24 100       1086 if ($no_naninf) {
525             return $is_perl_safe ?
526             sub {
527 222     222   659122 '( '.$_PerlNum->inline_check($_[1]).' || '.$_BlessedFloat_param->inline_check($_[1]).' )'.
528             ' && '.$_NumRange_param->inline_check($_[1])
529             } :
530 78     78   470113 sub { $_BlessedFloat_param->inline_check($_[1]).' && '.$_NumRange_param->inline_check($_[1]) }
531 12 100       85 ;
532             }
533             else {
534             return $is_perl_safe ?
535             sub {
536 222     222   2228455 '( '.$_PerlNum->inline_check($_[1]).' || '.$_BlessedFloat_param->inline_check($_[1]).' )'.
537             ' && ( '.$_NumRange_param->inline_check($_[1]).' || '.$_NaNInf->inline_check($_[1]).' )'
538             } :
539 78     78   2212860 sub { $_BlessedFloat_param->inline_check($_[1]).' && ('.$_NumRange_param->inline_check($_[1]).' || '.$_NaNInf->inline_check($_[1]).')' }
540 12 100       114 ;
541             }
542             }
543              
544             $meta->add_type(
545             name => 'FloatBinary',
546             parent => $_FloatSafeNum,
547             library => __PACKAGE__,
548             # kinda pointless without the parameters
549             constraint_generator => sub {
550             my $self = $Type::Tiny::parameterize_type;
551             my ($bits, $ebits) = (shift, shift);
552             $bits =~ /\A[0-9]+\z/ or _croak( "First parameter to FloatBinary[`b, `e] expected to be a positive integer; got $bits");
553             $ebits =~ /\A[0-9]+\z/ or _croak("Second parameter to FloatBinary[`b, `e] expected to be a positive integer; got $ebits");
554              
555             my $sbits = $bits - 1 - $ebits; # remove sign bit and exponent bits = significand precision
556              
557             # MAX = (2 - 2**(-$sbits-1)) * 2**($ebits-1)
558             my $emax = $bigtwo->copy->bpow($ebits-1)->bsub(1); # Y = (2**($ebits-1)-1)
559             my $smin = $bigtwo->copy->bpow(-$sbits-1)->bmul(-1)->badd(2); # Z = (2 - X) = -X + 2 (where X = 2**(-$sbits-1) )
560             my $max = $bigtwo->copy->bpow($emax)->bmul($smin); # MAX = 2**Y * Z
561              
562             my $digits = ceil( $sbits * _BASE2_LOG );
563              
564             my $is_perl_safe = (
565             Data::Float::significand_bits >= $sbits &&
566             Data::Float::max_finite_exp >= 2 ** $ebits - 1 &&
567             Data::Float::have_infinite &&
568             Data::Float::have_nan
569             );
570              
571             my $_NumRange_param = $_NumRange->parameterize(-$max, $max);
572              
573             Type::Tiny->new(
574             display_name => "FloatBinary[$bits, $ebits]",
575             parent => $self,
576             library => __PACKAGE__,
577             constraint => __real_constraint_generator($is_perl_safe, $digits, $_NumRange_param),
578             inlined => __real_inline_generator ($is_perl_safe, $digits, $_NumRange_param),
579             );
580             },
581             );
582              
583             $meta->add_type(
584             name => 'FloatDecimal',
585             parent => $_FloatSafeNum,
586             library => __PACKAGE__,
587             # kinda pointless without the parameters
588             constraint_generator => sub {
589             my $self = $Type::Tiny::parameterize_type;
590             my ($digits, $emax) = (shift, shift);
591             $digits =~ /\A[0-9]+\z/ or _croak( "First parameter to FloatDecimal[`d, `e] expected to be a positive integer; got $digits");
592             $emax =~ /\A[0-9]+\z/ or _croak("Second parameter to FloatDecimal[`d, `e] expected to be a positive integer; got $emax");
593              
594             # We're not going to worry about the (extreme) edge case that
595             # Perl might be compiled with decimal float NVs, but we still
596             # need to convert to base-2.
597             my $sbits = ceil( $digits / _BASE2_LOG );
598             my $emax2 = ceil( $emax / _BASE2_LOG );
599              
600             my $max = $bigten->copy->bpow($emax)->bmul( '9.'.('9' x ($digits-1)) );
601              
602             my $is_perl_safe = (
603             Data::Float::significand_bits >= $sbits &&
604             Data::Float::max_finite_exp >= $emax2 &&
605             Data::Float::have_infinite &&
606             Data::Float::have_nan
607             );
608              
609             my $_NumRange_param = $_NumRange->parameterize(-$max, $max);
610              
611             Type::Tiny->new(
612             display_name => "FloatDecimal[$digits, $emax]",
613             parent => $self,
614             library => __PACKAGE__,
615             constraint => __real_constraint_generator($is_perl_safe, $digits, $_NumRange_param),
616             inlined => __real_inline_generator ($is_perl_safe, $digits, $_NumRange_param),
617             );
618             },
619             );
620              
621             $meta->add_type(
622             name => 'FixedBinary',
623             parent => $_RealSafeNum,
624             library => __PACKAGE__,
625             # kinda pointless without the parameters
626             constraint_generator => sub {
627             my $self = $Type::Tiny::parameterize_type;
628             my ($bits, $scale) = (shift, shift);
629             $bits =~ /\A[0-9]+\z/ or _croak( "First parameter to FixedBinary[`b, `s] expected to be a positive integer; got $bits");
630             $scale =~ /\A[0-9]+\z/ or _croak("Second parameter to FixedBinary[`b, `s] expected to be a positive integer; got $scale");
631              
632             my $sbits = $bits - 1;
633              
634             # So, we have a base-10 scale and a base-2 set of $bits. Lovely.
635             # We can't actually figure out if it's Perl safe until we find the
636             # $max, adjust with the $scale, and then go BACK to base-2 limits.
637             my $div = $bigten->copy->bpow($scale);
638             my ($neg, $pos) = (
639             # bdiv returns (quo,rem) in list context :/
640             scalar $bigtwo->copy->bpow($sbits)->bmul(-1)->bdiv($div),
641             scalar $bigtwo->copy->bpow($sbits)->bsub(1)->bdiv($div),
642             );
643              
644             my $digits = ceil( $sbits * _BASE2_LOG );
645             my $emin2 = ceil( $scale / _BASE2_LOG );
646              
647             my $is_perl_safe = (
648             Data::Float::significand_bits >= $sbits &&
649             Data::Float::min_finite_exp <= -$emin2
650             );
651              
652             my $_NumRange_param = $_NumRange->parameterize($neg, $pos);
653              
654             Type::Tiny->new(
655             display_name => "FixedBinary[$bits, $scale]",
656             parent => $self,
657             library => __PACKAGE__,
658             constraint => __real_constraint_generator($is_perl_safe, $digits, $_NumRange_param, 1),
659             inlined => __real_inline_generator ($is_perl_safe, $digits, $_NumRange_param, 1),
660             );
661             },
662             );
663              
664             $meta->add_type(
665             name => 'FixedDecimal',
666             parent => $_RealSafeNum,
667             library => __PACKAGE__,
668             # kinda pointless without the parameters
669             constraint_generator => sub {
670             my $self = $Type::Tiny::parameterize_type;
671             my ($digits, $scale) = (shift, shift);
672             $digits =~ /\A[0-9]+\z/ or _croak( "First parameter to FixedDecimal[`d, `s] expected to be a positive integer; got $digits");
673             $scale =~ /\A[0-9]+\z/ or _croak("Second parameter to FixedDecimal[`d, `s] expected to be a positive integer; got $scale");
674              
675             my $sbits = ceil( $digits / _BASE2_LOG );
676             my $emin2 = ceil( $scale / _BASE2_LOG );
677              
678             my $is_perl_safe = (
679             Data::Float::significand_bits >= $sbits &&
680             Data::Float::min_finite_exp <= -$emin2
681             );
682              
683             my $div = $bigten->copy->bpow($scale);
684             my $max = $bigten->copy->bpow($digits)->bsub(1)->bdiv($div);
685              
686             my $_NumRange_param = $_NumRange->parameterize(-$max, $max);
687              
688             Type::Tiny->new(
689             display_name => "FixedDecimal[$digits, $scale]",
690             parent => $self,
691             library => __PACKAGE__,
692             constraint => __real_constraint_generator($is_perl_safe, $digits, $_NumRange_param, 1),
693             inlined => __real_inline_generator ($is_perl_safe, $digits, $_NumRange_param, 1),
694             );
695             },
696             );
697              
698             #############################################################################
699             # Character types
700              
701             $meta->add_type(
702             name => 'Char',
703             parent => Types::Standard::Str,
704             library => __PACKAGE__,
705             constraint => sub { length($_) == 1 }, # length() will do a proper Unicode char length
706             inlined => sub {
707             my ($self, $val) = @_;
708             $self->parent->inline_check($val)." && length($val) == 1";
709             },
710             constraint_generator => sub {
711             my $self = $Type::Tiny::parameterize_type;
712             my ($bits) = (shift);
713             $bits =~ /\A[0-9]+\z/ or _croak("Parameter to Char[`b] expected to be a positive integer; got $bits");
714              
715             Type::Tiny->new(
716             display_name => "Char[$bits]",
717             parent => $self,
718             library => __PACKAGE__,
719             constraint => sub { ord($_) < 2**$bits },
720             inlined => sub {
721             my $val = $_[1];
722             Types::Standard::Str->inline_check($val)." && length($val) == 1 && ord($val) < 2**$bits";
723             },
724             );
725             },
726             );
727              
728             42;
729              
730             __END__