—package
Types::Numbers;
our
$AUTHORITY
=
'cpan:GSG'
;
# ABSTRACT: Type constraints for numbers
use
version;
our
$VERSION
=
'v1.0.1'
;
# VERSION
#############################################################################
# Modules
use
v5.8.8;
use
strict;
use
warnings;
our
@EXPORT_OK
= ();
use
Type::Library -base;
use
Type::Tiny::Union;
use
Scalar::Util 1.20 (
qw(blessed looks_like_number)
);
# support for overloaded/blessed looks_like_number
use
Data::Float;
use
Data::Integer;
use
constant {
_BASE2_LOG
=>
log
(2) /
log
(10),
};
#pod =encoding utf8
#pod
#pod =head1 DESCRIPTION
#pod
#pod Because we deal with numbers every day in our programs and modules, this is an extensive
#pod L<Type::Tiny> library of number validations. Like L<Type::Tiny>, these types work with
#pod all modern OO platforms and as a standalone type system.
#pod
#pod =cut
#############################################################################
# Basic globals
my
$bigtwo
= Math::BigFloat->new(2);
my
$bigten
= Math::BigFloat->new(10);
# Large 64-bit floats (long doubles) tend to stringify themselves in exponent notation, even
# though the number is still pristine. IOW, the numeric form is perfect, but the string form
# loses information. This can be a problem for stringified inlines.
my
@df_max_int_parts
= Data::Float::float_parts( Data::Float::max_integer );
my
$DF_MAX_INT
=
$bigtwo
->copy->bpow(
$df_max_int_parts
[1])->bmul(
$df_max_int_parts
[2])->as_int;
my
$SAFE_NUM_MIN
= Math::BigInt->new(
Data::Integer::min_signed_natint <
$DF_MAX_INT
* -1 ?
Data::Integer::min_signed_natint :
$DF_MAX_INT
* -1
);
my
$SAFE_NUM_MAX
= Math::BigInt->new(
Data::Integer::max_unsigned_natint >
$DF_MAX_INT
* 1 ?
Data::Integer::max_unsigned_natint :
$DF_MAX_INT
* 1,
);
my
$meta
= __PACKAGE__->meta;
#############################################################################
# Framework types
### TODO: Coercions where safe ###
#pod =head1 TYPES
#pod
#pod =head2 Overview
#pod
#pod All of these types strive for the accurate storage and validation of many different types of
#pod numbers, including some storage types that Perl doesn't natively support.
#pod
#pod The hierarchy of the types is as follows:
#pod
#pod (T:S = From Types::Standard)
#pod (~T:C:N = Based on Types::Common::Numeric types)
#pod
#pod Item (T:S)
#pod Defined (T:S)
#pod NumLike
#pod NumRange[`n, `p] (~T:C:N)
#pod PositiveNum (~T:C:N)
#pod PositiveOrZeroNum (~T:C:N)
#pod NegativeNum (~T:C:N)
#pod NegativeOrZeroNum (~T:C:N)
#pod IntLike
#pod SignedInt[`b]
#pod UnsignedInt[`b]
#pod IntRange[`n, `p] (~T:C:N)
#pod PositiveInt (~T:C:N)
#pod PositiveOrZeroInt (~T:C:N)
#pod NegativeInt (~T:C:N)
#pod NegativeOrZeroInt (~T:C:N)
#pod SingleDigit (~T:C:N)
#pod PerlNum
#pod PerlSafeInt
#pod PerlSafeFloat
#pod BlessedNum[`d]
#pod BlessedInt[`d]
#pod BlessedFloat[`d]
#pod NaN
#pod Inf[`s]
#pod FloatSafeNum
#pod FloatBinary[`b, `e]
#pod FloatDecimal[`d, `e]
#pod RealNum
#pod RealSafeNum
#pod FixedBinary[`b, `s]
#pod FixedDecimal[`d, `s]
#pod
#pod Value (T:S)
#pod Str (T:S)
#pod Char[`b]
#pod
#pod =head2 Basic types
#pod
#pod =head3 NumLike
#pod
#pod Behaves like C<LaxNum> from L<Types::Standard>, but will also accept blessed number types. Unlike
#pod C<StrictNum>, it will accept C<NaN> and C<Inf> numbers.
#pod
#pod =cut
# Moose and Type::Tiny types both don't seem to support Math::Big* = Num.
# So, we have to start almost from stratch.
my
$_NumLike
=
$meta
->add_type(
name
=>
'NumLike'
,
parent
=> Types::Standard::Defined,
library
=> __PACKAGE__,
constraint
=>
sub
{ looks_like_number
$_
},
inlined
=>
sub
{
"Scalar::Util::looks_like_number($_[1])"
},
);
#pod =head3 NumRange[`n, `p]
#pod
#pod Only accepts numbers within a certain range. By default, the two parameters are the minimums and maximums,
#pod inclusive. However, this type is also compatible with a few different parameter styles, a la L<Types::Common::Numeric>.
#pod
#pod The minimum/maximums can be omitted or undefined. Or two extra boolean parameters can be added to specify exclusivity:
#pod
#pod NumRange[0.1, 10.0, 0, 0] # both inclusive
#pod NumRange[0.1, 10.0, 0, 1] # exclusive maximum, so 10.0 is invalid
#pod NumRange[0.1, 10.0, 1, 0] # exclusive minimum, so 0.1 is invalid
#pod NumRange[0.1, 10.0, 1, 1] # both exclusive
#pod
#pod NumRange[0.1] # lower bound check only
#pod NumRange[undef, 10.0] # upper bound check only
#pod NumRange[0.1, undef, 1] # lower bound check only, exclusively
#pod NumRange[undef, 10.0, 1, 1] # upper bound check only, exclusively (third param ignored)
#pod
#pod =cut
my
$_NumRange
=
$meta
->add_type(
name
=>
'NumRange'
,
parent
=>
$_NumLike
,
library
=> __PACKAGE__,
# kinda pointless without the parameters
constraint_generator
=>
sub
{
my
$self
=
$Type::Tiny::parameterize_type
;
my
(
$min
,
$max
,
$min_excl
,
$max_excl
) =
@_
;
!
defined
$min
or looks_like_number(
$min
) or _croak(
"First parameter to NumRange[`n, `p] expected to be a number; got $min"
);
!
defined
$max
or looks_like_number(
$max
) or _croak(
"Second parameter to NumRange[`n, `p] expected to be a number; got $max"
);
!
defined
$min_excl
or Bool->check(
$min_excl
) or _croak(
"Third parameter to NumRange[`n, `p] expected to be a boolean; got $min_excl"
);
!
defined
$max_excl
or Bool->check(
$max_excl
) or _croak(
"Fourth parameter to NumRange[`n, `p] expected to be a boolean; got $max_excl"
);
$min_excl
= 0
unless
defined
$min_excl
;
$max_excl
= 0
unless
defined
$max_excl
;
my
(
$Imin
,
$Imax
) = (
$min
,
$max
);
$Imin
= blessed(
$min
).
"\->new('$min')"
if
defined
$min
&& blessed
$min
;
$Imax
= blessed(
$max
).
"\->new('$max')"
if
defined
$max
&& blessed
$max
;
my
$display_name
=
'NumRange['
.
join
(
', '
,
map
{
defined
$_
?
$_
:
'undef'
} (
$min
,
$max
,
$min_excl
,
$max_excl
) ).
']'
;
Type::Tiny->new(
display_name
=>
$display_name
,
parent
=>
$self
,
library
=> __PACKAGE__,
constraint
=>
sub
{
my
$val
=
$_
;
# AND checks, so return false on the logically-opposite checks (>= --> <)
if
(
defined
$min
) {
return
!!0
if
$val
<
$min
;
return
!!0
if
$val
==
$min
&&
$min_excl
;
}
if
(
defined
$max
) {
return
!!0
if
$val
>
$max
;
return
!!0
if
$val
==
$max
&&
$max_excl
;
}
# NaN still passes both NumLike and the anti-checks above, so we use this seemingly-paradoxical
# logical check here to reject it
return
$val
==
$val
;
},
inlined
=>
sub
{
my
(
$self
,
$val
) =
@_
;
my
@checks
= (
undef
);
# parent check
push
@checks
,
join
(
' '
,
$val
, (
$min_excl
?
'>'
:
'>='
),
$Imin
)
if
defined
$min
;
push
@checks
,
join
(
' '
,
$val
, (
$max_excl
?
'<'
:
'<='
),
$Imax
)
if
defined
$max
;
@checks
;
},
);
},
);
# we need to optimize out all of the NumLike checks
my
$_NumRange_perlsafe
= Type::Tiny->new(
display_name
=>
"_NumRange_perlsafe"
,
parent
=>
$_NumLike
,
# no equals because MAX+1 = MAX after truncation
constraint
=>
sub
{
$_
>
$SAFE_NUM_MIN
&&
$_
<
$SAFE_NUM_MAX
},
inlined
=>
sub
{
"$_ > "
.
$SAFE_NUM_MIN
,
"$_ < "
.
$SAFE_NUM_MAX
},
);
#pod =head3 PerlNum
#pod
#pod Exactly like C<LaxNum>, but with a different parent. Only accepts unblessed numbers.
#pod
#pod =cut
my
$_PerlNum
=
$meta
->add_type(
name
=>
'PerlNum'
,
parent
=>
$_NumLike
,
library
=> __PACKAGE__,
# LaxNum has parental constraints that matter, so we can't just blindly steal its own
# constraint by itself. The inlined sub, OTOH, is self-sufficient.
constraint
=>
sub
{ Types::Standard::LaxNum->check(
$_
) },
inlined
=> Types::Standard::LaxNum->inlined,
);
#pod =head3 BlessedNum
#pod
#pod Only accepts blessed numbers. A blessed number would be using something like L<Math::BigInt> or
#pod L<Math::BigFloat>. It doesn't directly C<isa> check those classes, just that the number is
#pod blessed.
#pod
#pod =head3 BlessedNum[`d]
#pod
#pod A blessed number that supports at least certain amount of digit accuracy. The blessed number must
#pod support the C<accuracy> or C<div_scale> method.
#pod
#pod For example, C<BlessedNum[40]> would work for the default settings of L<Math::BigInt>, and supports
#pod numbers at least as big as 128-bit integers.
#pod
#pod =cut
my
$_BlessedNum
=
$meta
->add_type( Type::Tiny::Intersection->new(
name
=>
'BlessedNum'
,
display_name
=>
'BlessedNum'
,
library
=> __PACKAGE__,
type_constraints
=> [
$_NumLike
, Types::Standard::Object ],
constraint_generator
=>
sub
{
my
$self
=
$Type::Tiny::parameterize_type
;
my
$digits
=
shift
;
$digits
=~ /\A[0-9]+\z/ or _croak(
"Parameter to BlessedNum[`d] expected to be a positive integer; got $digits"
);
Type::Tiny->new(
display_name
=>
"BlessedNum[$digits]"
,
parent
=>
$self
,
library
=> __PACKAGE__,
constraint
=>
sub
{
my
$val
=
$_
;
$val
->can(
'accuracy'
) &&
$val
->accuracy &&
$val
->accuracy >=
$digits
||
$val
->can(
'div_scale'
) &&
$val
->div_scale &&
$val
->div_scale >=
$digits
;
},
inlined
=>
sub
{
my
(
$self
,
$val
) =
@_
;
return
(
undef
,
"$val->can('accuracy') && $val->accuracy && $val->accuracy >= $digits || "
.
"$val->can('div_scale') && $val->div_scale && $val->div_scale >= $digits"
);
},
);
},
) );
#pod =head3 NaN
#pod
#pod A "not-a-number" value, either embedded into the Perl native float or a blessed C<NaN>,
#pod checked via C<is_nan>.
#pod
#pod =cut
my
$_NaN
=
$meta
->add_type(
name
=>
'NaN'
,
parent
=>
$_NumLike
,
library
=> __PACKAGE__,
constraint
=>
sub
{
my
$val
=
$_
;
Types::Standard::Object->check(
$val
) &&
$val
->can(
'is_nan'
) &&
$val
->is_nan ||
Data::Float::float_is_nan(
$val
);
},
inlined
=>
sub
{
my
(
$self
,
$val
) =
@_
;
return
(
undef
,
Types::Standard::Object->inline_check(
$val
).
" && $val->can('is_nan') && $val->is_nan ||"
.
"Data::Float::float_is_nan($val)"
);
},
);
#pod =head3 Inf
#pod
#pod An infinity value, either embedded into the Perl native float or a blessed C<Inf>, checked via
#pod C<is_inf>.
#pod
#pod =head3 Inf[`s]
#pod
#pod Inf['+']
#pod Inf['-']
#pod
#pod An infinity value with a certain sign, either embedded into the Perl native float or a blessed
#pod C<Inf>, checked via C<is_inf>. The parameter must be a plus or minus character.
#pod
#pod =cut
my
$_Inf
=
$meta
->add_type(
name
=>
'Inf'
,
parent
=>
$_NumLike
,
library
=> __PACKAGE__,
constraint
=>
sub
{
my
$val
=
$_
;
Types::Standard::Object->check(
$val
) &&
$val
->can(
'is_inf'
) && (
$val
->is_inf(
'+'
) ||
$val
->is_inf(
'-'
)) ||
Data::Float::float_is_infinite(
$val
);
},
inlined
=>
sub
{
my
(
$self
,
$val
) =
@_
;
return
(
undef
,
Types::Standard::Object->inline_check(
$val
).
" && $val->can('is_inf') && ($val->is_inf('+') || $val->is_inf('-')) ||"
.
"Data::Float::float_is_infinite($val)"
);
},
constraint_generator
=>
sub
{
my
$self
=
$Type::Tiny::parameterize_type
;
my
$sign
=
shift
;
$sign
=~ /\A[+\-]\z/ or _croak(
"Parameter to Inf[`s] expected to be a plus or minus sign; got $sign"
);
Type::Tiny->new(
display_name
=>
"Inf[$sign]"
,
parent
=>
$self
,
library
=> __PACKAGE__,
constraint
=>
sub
{
my
$val
=
$_
;
Types::Standard::Object->check(
$val
) &&
$val
->can(
'is_inf'
) &&
$val
->is_inf(
$sign
) ||
Data::Float::float_is_infinite(
$val
) && Data::Float::float_sign(
$val
) eq
$sign
;
},
inlined
=>
sub
{
my
(
$self
,
$val
) =
@_
;
return
(
undef
,
Types::Standard::Object->inline_check(
$val
).
" && $val->can('is_inf') && $val->is_inf('$sign') || "
.
"Data::Float::float_is_infinite($val) && Data::Float::float_sign($val) eq '$sign'"
);
},
);
},
);
# this is used a lot for floats, but we need to optimize out all of the NumLike checks
my
$_NaNInf
= Type::Tiny::Union->new(
type_constraints
=> [
$_NaN
,
$_Inf
],
)->create_child_type(
name
=>
'NaNInf'
,
constraint
=>
sub
{
# looks_like_number($_) &&
Types::Standard::Object->check(
$_
) && (
$_
->can(
'is_nan'
) &&
$_
->is_nan ||
$_
->can(
'is_inf'
) && (
$_
->is_inf(
'+'
) ||
$_
->is_inf(
'-'
))
) || Data::Float::float_is_nan(
$_
) || Data::Float::float_is_infinite(
$_
)
},
inlined
=>
sub
{
my
(
$self
,
$val
) =
@_
;
# looks_like_number($val) &&
Types::Standard::Object->inline_check(
$val
).
" && ( "
.
# NOTE: A && (B) || C || D, so don't list-separate
"$val->can('is_nan') && $val->is_nan || "
.
"$val->can('is_inf') && ($val->is_inf('+') || $val->is_inf('-')) "
.
") || Data::Float::float_is_nan($val) || Data::Float::float_is_infinite($val)"
;
},
);
my
$_not_NaNInf
=
$_NaNInf
->complementary_type;
#pod =head3 RealNum
#pod
#pod Like L</NumLike>, but does not accept NaN or Inf. Closer to the spirit of C<StrictNum>, but
#pod accepts blessed numbers as well.
#pod
#pod =cut
my
$_RealNum
=
$meta
->add_type( Type::Tiny::Intersection->new(
name
=>
'RealNum'
,
display_name
=>
'RealNum'
,
library
=> __PACKAGE__,
type_constraints
=> [
$_NumLike
,
$_not_NaNInf
],
) );
#############################################################################
# Integer types
#pod =head2 Integers
#pod
#pod =cut
# Helper subs
sub
__integer_bits_vars {
my
(
$bits
,
$is_unsigned
) =
@_
;
my
$sbits
=
$bits
- 1;
my
(
$neg
,
$spos
,
$upos
) = (
$bigtwo
->copy->bpow(
$sbits
)->bmul(-1),
$bigtwo
->copy->bpow(
$sbits
)->bsub(1),
$bigtwo
->copy->bpow(
$bits
)->bsub(1),
);
my
$sdigits
= ceil(
$sbits
* _BASE2_LOG );
my
$udigits
= ceil(
$bits
* _BASE2_LOG );
return
$is_unsigned
?
(0,
$upos
,
$udigits
) :
(
$neg
,
$spos
,
$sdigits
)
;
}
#pod =head3 IntLike
#pod
#pod Behaves like C<Int> from L<Types::Standard>, but will also accept blessed number types and integers
#pod in E notation. There are no expectations of storage limitations here. (See L</SignedInt> for
#pod that.)
#pod
#pod =cut
### XXX: This string equality check is necessary because Math::BigInt seems to think 1.5 == 1.
### However, this is problematic with long doubles that stringify into E notation.
my
$_IntLike
=
$meta
->add_type(
name
=>
'IntLike'
,
parent
=>
$_NumLike
,
library
=> __PACKAGE__,
constraint
=>
sub
{ /\d+/ &&
int
(
$_
) ==
$_
&& (
int
(
$_
) eq
$_
|| !
ref
(
$_
)) },
inlined
=>
sub
{
my
(
$self
,
$val
) =
@_
;
(
undef
,
"$val =~ /\\d+/"
,
"int($val) == $val"
,
"(int($val) eq $val || !ref($val))"
);
},
);
#pod =head3 IntRange[`n, `p]
#pod
#pod Only accepts integers within a certain range. By default, the two parameters are the minimums and maximums,
#pod inclusive. Though, the minimum/maximums can be omitted or undefined.
#pod
#pod =cut
my
$_IntRange
=
$meta
->add_type(
name
=>
'IntRange'
,
parent
=>
$_IntLike
,
library
=> __PACKAGE__,
# kinda pointless without the parameters
constraint_generator
=>
sub
{
my
$self
=
$Type::Tiny::parameterize_type
;
my
(
$min
,
$max
) =
@_
;
!
defined
$min
or looks_like_number(
$min
) or _croak(
"First parameter to IntRange[`n, `p] expected to be a number; got $min"
);
!
defined
$max
or looks_like_number(
$max
) or _croak(
"Second parameter to IntRange[`n, `p] expected to be a number; got $max"
);
my
(
$Imin
,
$Imax
) = (
$min
,
$max
);
$Imin
= blessed(
$min
).
"\->new('$min')"
if
defined
$min
&& blessed
$min
;
$Imax
= blessed(
$max
).
"\->new('$max')"
if
defined
$max
&& blessed
$max
;
my
$display_name
=
'IntRange['
.
join
(
', '
,
map
{
defined
$_
?
$_
:
'undef'
} (
$min
,
$max
) ).
']'
;
Type::Tiny->new(
display_name
=>
$display_name
,
parent
=>
$self
,
library
=> __PACKAGE__,
constraint
=>
sub
{
my
$val
=
$_
;
# AND checks, so return false on the logically-opposite checks (>= --> <)
return
!!0
if
defined
$min
&&
$val
<
$min
;
return
!!0
if
defined
$max
&&
$val
>
$max
;
return
!!1;
},
inlined
=>
sub
{
my
(
$self
,
$val
) =
@_
;
my
@checks
= (
undef
);
# parent check
push
@checks
,
"$val >= $Imin"
if
defined
$min
;
push
@checks
,
"$val <= $Imax"
if
defined
$max
;
@checks
;
},
);
},
);
#pod =head3 PerlSafeInt
#pod
#pod A Perl (unblessed) integer number than can safely hold the integer presented. This varies between
#pod 32-bit and 64-bit versions of Perl.
#pod
#pod For example, for most 32-bit versions of Perl, the largest integer than can be safely held in a
#pod 4-byte NV (floating point number) is C<9007199254740992>. Numbers can go higher than that, but due
#pod to the NV's mantissa length (accuracy), information is lost beyond this point.
#pod
#pod In this case, C<...992> would pass and C<...993> would fail.
#pod
#pod (Technically, the max integer is C<...993>, but we can't tell the difference between C<...993> and
#pod C<...994>, so the cut off point is C<...992>, inclusive.)
#pod
#pod Be aware that Perls compiled with "long doubles" have a unique problem with storage and information
#pod loss: their number form maintains accuracy while their (default) stringified form loses
#pod information. For example, take the max safe integer for a long double:
#pod
#pod $num = 18446744073709551615;
#pod say $num; # 1.84467440737095516e+19
#pod say $num == 18446744073709551615; # true, so the full number is still there
#pod say sprintf('%u', $num); # 18446744073709551615
#pod
#pod These numbers are considered safe for storage. If this is not preferred, consider a simple C</e/>
#pod check for stringified E notation.
#pod
#pod =cut
my
$_PerlSafeInt
=
$meta
->add_type( Type::Tiny::Intersection->new(
library
=> __PACKAGE__,
type_constraints
=> [
$_PerlNum
,
$_IntLike
,
$_NumRange_perlsafe
],
)->create_child_type(
name
=>
'PerlSafeInt'
,
library
=> __PACKAGE__,
inlined
=>
sub
{
my
$val
=
$_
[1];
(
"defined $val"
,
"!ref($val)"
,
"$val =~ /\\d+/"
,
"int($val) == $val"
,
$_NumRange_perlsafe
->inline_check(
$val
));
},
) );
#pod =head3 BlessedInt
#pod
#pod A blessed number than is holding an integer. (A L<Math::BigFloat> with an integer value would
#pod still pass.)
#pod
#pod =head3 BlessedInt[`d]
#pod
#pod A blessed number holding an integer of at most C<`d> digits (inclusive). The blessed number
#pod container must also have digit accuracy to support this number. (See L</BlessedNum[`d]>.)
#pod
#pod =cut
my
$_BlessedInt
=
$meta
->add_type( Type::Tiny::Intersection->new(
library
=> __PACKAGE__,
type_constraints
=> [
$_BlessedNum
,
$_IntLike
],
)->create_child_type(
name
=>
'BlessedInt'
,
library
=> __PACKAGE__,
inlined
=>
sub
{
my
$val
=
$_
[1];
Types::Standard::Object->inline_check(
$val
),
"$val =~ /\\d+/"
,
"int($val) == $val"
,
"int($val) eq $val"
;
},
constraint_generator
=>
sub
{
my
$self
=
$Type::Tiny::parameterize_type
;
my
$digits
=
shift
;
$digits
=~ /\A[0-9]+\z/ or _croak(
"Parameter to BlessedInt[`d] expected to be a positive integer; got $digits"
);
my
$_BlessedNum_param
=
$_BlessedNum
->parameterize(
$digits
);
Type::Tiny->new(
display_name
=>
"BlessedInt[$digits]"
,
parent
=>
$self
,
library
=> __PACKAGE__,
constraint
=>
sub
{
$_IntLike
->check(
$_
) &&
$_BlessedNum_param
->check(
$_
) &&
do
{
my
$num
=
$_
;
$num
=~ s/\D+//g;
length
(
$num
) <=
$digits
}
},
inlined
=>
sub
{
my
$val
=
$_
[1];
return
(
$_BlessedNum_param
->inline_check(
$val
),
"$val =~ /\\d+/"
,
"int($val) == $val"
,
"int($val) eq $val"
,
"do { "
.
'my $num = '
.
$val
.
'; '
.
'$num =~ s/\D+//g; '
.
'length($num) <= '
.
$digits
.
' '
.
'}'
);
},
);
},
) );
#pod =head3 SignedInt
#pod
#pod A signed integer (blessed or otherwise) that can safely hold its own number. This is different
#pod than L</IntLike>, which doesn't check for storage limitations.
#pod
#pod =head3 SignedInt[`b]
#pod
#pod A signed integer that can hold a C<`b> bit number and is within those boundaries. One bit is
#pod reserved for the sign, so the max limit on a 32-bit integer is actually C<2**31-1> or
#pod C<2147483647>.
#pod
#pod =cut
$meta
->add_type( Type::Tiny::Union->new(
#parent => $_IntLike,
library
=> __PACKAGE__,
type_constraints
=> [
$_PerlSafeInt
,
$_BlessedInt
],
)->create_child_type(
name
=>
'SignedInt'
,
library
=> __PACKAGE__,
inlined
=>
sub
{
my
$val
=
$_
[1];
return
(
$_IntLike
->inline_check(
$val
),
$_NumRange_perlsafe
->inline_check(
$val
).
' || '
.Types::Standard::Object->inline_check(
$val
)
);
},
constraint_generator
=>
sub
{
my
$self
=
$Type::Tiny::parameterize_type
;
my
$bits
=
shift
;
$bits
=~ /\A[0-9]+\z/ or _croak(
"Parameter to SignedInt[`b] expected to be a positive integer; got $bits"
);
my
(
$min
,
$max
,
$digits
) = __integer_bits_vars(
$bits
, 0);
my
$_BlessedInt_param
=
$_BlessedInt
->parameterize(
$digits
);
my
$_NumRange_param
=
$_NumRange
->parameterize(
$min
,
$max
);
Type::Tiny::Intersection->new(
library
=> __PACKAGE__,
type_constraints
=> [
$self
, (
$_PerlSafeInt
|
$_BlessedInt_param
),
$_NumRange_param
],
)->create_child_type(
display_name
=>
"SignedInt[$bits]"
,
inlined
=>
sub
{
my
$val
=
$_
[1];
return
(
$_PerlSafeInt
->inline_check(
$val
).
' || '
.
$_BlessedInt_param
->inline_check(
$val
),
$_NumRange_param
->inline_check(
$val
)
);
},
);
},
) );
#pod =head3 UnsignedInt
#pod
#pod Like L</SignedInt>, but with a minimum boundary of zero.
#pod
#pod =head3 UnsignedInt[`b]
#pod
#pod Like L</SignedInt[`b]>, but for unsigned integers. Also, unsigned integers gain their extra bit,
#pod so the maximum is twice as high.
#pod
#pod =cut
$meta
->add_type(
name
=>
'UnsignedInt'
,
parent
=>
$_IntLike
,
library
=> __PACKAGE__,
constraint
=>
sub
{
$_IntLike
->check(
$_
) &&
$_
>= 0 && (
$_PerlSafeInt
->check(
$_
) ||
$_BlessedNum
->check(
$_
)) },
inlined
=>
sub
{
my
$val
=
$_
[1];
(
undef
,
"$val >= 0"
,
'('
.
$_NumRange_perlsafe
->inline_check(
$val
).
' || '
.Types::Standard::Object->inline_check(
$val
).
')'
);
},
constraint_generator
=>
sub
{
my
$self
=
$Type::Tiny::parameterize_type
;
my
$bits
=
shift
;
$bits
=~ /\A[0-9]+\z/ or _croak(
"Parameter to UnsignedInt[`b] expected to be a positive integer; got $bits"
);
my
(
$min
,
$max
,
$digits
) = __integer_bits_vars(
$bits
, 1);
my
$_BlessedNum_param
=
$_BlessedNum
->parameterize(
$digits
);
# IntLike check extracted out
my
$_NumRange_param
=
$_NumRange
->parameterize(
$min
,
$max
);
# inline will already have the IntLike check, and maybe not need the extra NumRange check
my
$perlsafe_inline
=
$min
>=
$SAFE_NUM_MIN
&&
$max
<=
$SAFE_NUM_MAX
?
sub
{ Types::Standard::Str->inline_check(
$_
[0]) } :
sub
{
'('
.Types::Standard::Str->inline_check(
$_
[0]).
' && '
.
$_NumRange_perlsafe
->inline_check(
$_
[0]).
')'
}
;
Type::Tiny->new(
display_name
=>
"UnsignedInt[$bits]"
,
parent
=>
$self
,
library
=> __PACKAGE__,
constraint
=>
sub
{
$_IntLike
->check(
$_
) &&
$_NumRange_param
->check(
$_
) &&
(
$_PerlSafeInt
->check(
$_
) ||
$_BlessedNum_param
->check(
$_
));
},
inlined
=>
sub
{
my
$val
=
$_
[1];
return
(
$_IntLike
->inline_check(
$val
),
$_NumRange_param
->inline_check(
$val
),
$perlsafe_inline
->(
$val
).
' || '
.
$_BlessedNum_param
->inline_check(
$val
)
);
},
);
},
);
#############################################################################
# Float/fixed types
#pod =head2 Floating-point numbers
#pod
#pod =head3 PerlSafeFloat
#pod
#pod A Perl native float that is in the "integer safe" range, or is a NaN/Inf value.
#pod
#pod This doesn't guarantee that every single fractional number is going to retain all of its
#pod information here. It only guarantees that the whole number will be retained, even if the
#pod fractional part is partly or completely lost.
#pod
#pod =cut
my
$_PerlSafeFloat
=
$meta
->add_type(
name
=>
'PerlSafeFloat'
,
parent
=>
$_PerlNum
,
library
=> __PACKAGE__,
constraint
=>
sub
{
$_NumRange_perlsafe
->check(
$_
) || Data::Float::float_is_nan(
$_
) || Data::Float::float_is_infinite(
$_
) },
inlined
=>
sub
{
my
(
$self
,
$val
) =
@_
;
return
(
undef
,
$_NumRange_perlsafe
->inline_check(
$val
).
" || Data::Float::float_is_nan($val) || Data::Float::float_is_infinite($val)"
);
},
);
#pod =head3 BlessedFloat
#pod
#pod A blessed number that will support fractional numbers. A L<Math::BigFloat> number will pass,
#pod whereas a L<Math::BigInt> number will fail. However, if that L<Math::BigInt> number is capable of
#pod upgrading to a L<Math::BigFloat>, it will pass.
#pod
#pod =head3 BlessedFloat[`d]
#pod
#pod A float-capable blessed number that supports at least certain amount of digit accuracy. The number
#pod itself is not boundary checked, as it is excessively difficult to figure out the exact dimensions
#pod of a floating point number. It would also not be useful for numbers like C<0.333333...> to fail
#pod checks.
#pod
#pod =cut
my
$_BlessedFloat
=
$meta
->add_type(
name
=>
'BlessedFloat'
,
parent
=>
$_BlessedNum
,
library
=> __PACKAGE__,
constraint
=>
sub
{ blessed(
$_
)->new(1.2) == 1.2 },
inlined
=>
sub
{
my
(
$self
,
$val
) =
@_
;
undef
,
"Scalar::Util::blessed($val)\->new(1.2) == 1.2"
;
},
constraint_generator
=>
sub
{
my
$self
=
$Type::Tiny::parameterize_type
;
my
$digits
=
shift
;
$digits
=~ /\A[0-9]+\z/ or _croak(
"Parameter to BlessedFloat[`d] expected to be a positive integer; got $digits"
);
my
$_BlessedNum_param
=
$_BlessedNum
->parameterize(
$digits
);
Type::Tiny->new(
display_name
=>
"BlessedFloat[$digits]"
,
parent
=>
$self
,
library
=> __PACKAGE__,
constraint
=>
sub
{
$_BlessedNum_param
->check(
$_
) && blessed(
$_
)->new(1.2) == 1.2 },
inlined
=>
sub
{
my
(
$self
,
$val
) =
@_
;
(
$_BlessedNum_param
->inline_check(
$val
),
"Scalar::Util::blessed($val)\->new(1.2) == 1.2"
);
},
);
},
);
#pod =head3 FloatSafeNum
#pod
#pod A Union of L</PerlSafeFloat> and L</BlessedFloat>. In other words, a float-capable number with
#pod some basic checks to make sure information is retained.
#pod
#pod =cut
my
$_FloatSafeNum
=
$meta
->add_type( Type::Tiny::Union->new(
library
=> __PACKAGE__,
type_constraints
=> [
$_PerlSafeFloat
,
$_BlessedFloat
],
)->create_child_type(
name
=>
'FloatSafeNum'
,
library
=> __PACKAGE__,
inlined
=>
sub
{
my
(
$self
,
$val
) =
@_
;
return
(
undef
,
"!ref($val)"
,
"Scalar::Util::blessed($val)->new(1.2) == 1.2"
,
'('
.
$_NumRange_perlsafe
->inline_check(
$val
).
" || Data::Float::float_is_nan($val) || Data::Float::float_is_infinite($val)"
.
') || '
.Types::Standard::Object->inline_check(
$val
),
);
},
) );
#pod =head3 FloatBinary[`b, `e]
#pod
#pod A floating-point number that can hold a C<`b> bit number with C<`e> bits of exponent, and is within
#pod those boundaries (or is NaN/Inf). The bit breakdown follows traditional IEEE 754 floating point
#pod standards. For example:
#pod
#pod FloatBinary[32, 8] =
#pod 32 bits total (`b)
#pod 23 bit mantissa (significand precision)
#pod 8 bit exponent (`e)
#pod 1 bit sign (+/-)
#pod
#pod Unlike the C<*Int> types, if Perl's native NV cannot support all dimensions of the floating-point
#pod number without losing information, then unblessed numbers are completely off the table. For
#pod example, assuming a 32-bit machine:
#pod
#pod (UnsignedInt[64])->check( 0 ) # pass
#pod (UnsignedInt[64])->check( 2 ** 30 ) # pass
#pod (UnsignedInt[64])->check( 2 ** 60 ) # fail, because 32-bit NVs can't safely hold it
#pod
#pod (FloatBinary[64, 11])->check( 0 ) # fail
#pod (FloatBinary[64, 11])->check( $any_unblessed_number ) # fail
#pod
#pod =cut
### NOTE: These two are very close to another type, but there's just too many variables
### to throw into a typical type
sub
__real_constraint_generator {
my
(
$is_perl_safe
,
$digits
,
$_NumRange_param
,
$no_naninf
) =
@_
;
my
$_BlessedFloat_param
=
$_BlessedFloat
->parameterize(
$digits
);
if
(
$no_naninf
) {
return
$is_perl_safe
?
sub
{ (
$_PerlNum
->check(
$_
) ||
$_BlessedFloat_param
->check(
$_
) ) &&
$_NumRange_param
->check(
$_
) } :
sub
{
$_BlessedFloat_param
->check(
$_
) &&
$_NumRange_param
->check(
$_
) }
;
}
else
{
return
$is_perl_safe
?
sub
{ (
$_PerlNum
->check(
$_
) ||
$_BlessedFloat_param
->check(
$_
) ) &&
$_NumRange_param
->check(
$_
) ||
$_NaNInf
->check(
$_
) } :
sub
{
$_BlessedFloat_param
->check(
$_
) && (
$_NumRange_param
->check(
$_
) ||
$_NaNInf
->check(
$_
) ); }
;
}
}
sub
__real_inline_generator {
my
(
$is_perl_safe
,
$digits
,
$_NumRange_param
,
$no_naninf
) =
@_
;
my
$_BlessedFloat_param
=
$_BlessedFloat
->parameterize(
$digits
);
if
(
$no_naninf
) {
return
$is_perl_safe
?
sub
{ (
$_PerlNum
->inline_check(
$_
[1]).
' || '
.
$_BlessedFloat_param
->inline_check(
$_
[1]),
$_NumRange_param
->inline_check(
$_
[1])
) } :
sub
{ (
$_BlessedFloat_param
->inline_check(
$_
[1]),
$_NumRange_param
->inline_check(
$_
[1])) }
;
}
else
{
return
$is_perl_safe
?
sub
{ (
$_PerlNum
->inline_check(
$_
[1]).
' || '
.
$_BlessedFloat_param
->inline_check(
$_
[1]),
$_NumRange_param
->inline_check(
$_
[1]).
' || '
.
$_NaNInf
->inline_check(
$_
[1])
) } :
sub
{ (
$_BlessedFloat_param
->inline_check(
$_
[1]),
$_NumRange_param
->inline_check(
$_
[1]).
' || '
.
$_NaNInf
->inline_check(
$_
[1])
) }
;
}
}
$meta
->add_type(
name
=>
'FloatBinary'
,
parent
=>
$_FloatSafeNum
,
library
=> __PACKAGE__,
# kinda pointless without the parameters
constraint_generator
=>
sub
{
my
$self
=
$Type::Tiny::parameterize_type
;
my
(
$bits
,
$ebits
) = (
shift
,
shift
);
$bits
=~ /\A[0-9]+\z/ or _croak(
"First parameter to FloatBinary[`b, `e] expected to be a positive integer; got $bits"
);
$ebits
=~ /\A[0-9]+\z/ or _croak(
"Second parameter to FloatBinary[`b, `e] expected to be a positive integer; got $ebits"
);
my
$sbits
=
$bits
- 1 -
$ebits
;
# remove sign bit and exponent bits = significand precision
# MAX = (2 - 2**(-$sbits-1)) * 2**($ebits-1)
my
$emax
=
$bigtwo
->copy->bpow(
$ebits
-1)->bsub(1);
# Y = (2**($ebits-1)-1)
my
$smin
=
$bigtwo
->copy->bpow(-
$sbits
-1)->bmul(-1)->badd(2);
# Z = (2 - X) = -X + 2 (where X = 2**(-$sbits-1) )
my
$max
=
$bigtwo
->copy->bpow(
$emax
)->bmul(
$smin
);
# MAX = 2**Y * Z
my
$digits
= ceil(
$sbits
* _BASE2_LOG );
my
$is_perl_safe
= (
Data::Float::significand_bits >=
$sbits
&&
Data::Float::max_finite_exp >= 2 **
$ebits
- 1 &&
Data::Float::have_infinite &&
Data::Float::have_nan
);
my
$_NumRange_param
=
$_NumRange
->parameterize(-
$max
,
$max
);
Type::Tiny->new(
display_name
=>
"FloatBinary[$bits, $ebits]"
,
parent
=>
$self
,
library
=> __PACKAGE__,
constraint
=> __real_constraint_generator(
$is_perl_safe
,
$digits
,
$_NumRange_param
),
inlined
=> __real_inline_generator (
$is_perl_safe
,
$digits
,
$_NumRange_param
),
);
},
);
#pod =head3 FloatDecimal[`d, `e]
#pod
#pod A floating-point number that can hold a C<`d> digit number with C<`e> digits of exponent. Modeled
#pod after the IEEE 754 "decimal" float. Rejects all Perl NVs that won't support the dimensions. (See
#pod L</FloatBinary[`b, `e]>.)
#pod
#pod =cut
$meta
->add_type(
name
=>
'FloatDecimal'
,
parent
=>
$_FloatSafeNum
,
library
=> __PACKAGE__,
# kinda pointless without the parameters
constraint_generator
=>
sub
{
my
$self
=
$Type::Tiny::parameterize_type
;
my
(
$digits
,
$emax
) = (
shift
,
shift
);
$digits
=~ /\A[0-9]+\z/ or _croak(
"First parameter to FloatDecimal[`d, `e] expected to be a positive integer; got $digits"
);
$emax
=~ /\A[0-9]+\z/ or _croak(
"Second parameter to FloatDecimal[`d, `e] expected to be a positive integer; got $emax"
);
# We're not going to worry about the (extreme) edge case that
# Perl might be compiled with decimal float NVs, but we still
# need to convert to base-2.
my
$sbits
= ceil(
$digits
/ _BASE2_LOG );
my
$emax2
= ceil(
$emax
/ _BASE2_LOG );
my
$max
=
$bigten
->copy->bpow(
$emax
)->bmul(
'9.'
.(
'9'
x (
$digits
-1)) );
my
$is_perl_safe
= (
Data::Float::significand_bits >=
$sbits
&&
Data::Float::max_finite_exp >=
$emax2
&&
Data::Float::have_infinite &&
Data::Float::have_nan
);
my
$_NumRange_param
=
$_NumRange
->parameterize(-
$max
,
$max
);
Type::Tiny->new(
display_name
=>
"FloatDecimal[$digits, $emax]"
,
parent
=>
$self
,
library
=> __PACKAGE__,
constraint
=> __real_constraint_generator(
$is_perl_safe
,
$digits
,
$_NumRange_param
),
inlined
=> __real_inline_generator (
$is_perl_safe
,
$digits
,
$_NumRange_param
),
);
},
);
#pod =head2 Fixed-point numbers
#pod
#pod =head3 RealSafeNum
#pod
#pod Like L</FloatSafeNum>, but rejects any NaN/Inf.
#pod
#pod =cut
my
$_RealSafeNum
=
$meta
->add_type( Type::Tiny::Intersection->new(
library
=> __PACKAGE__,
type_constraints
=> [
$_RealNum
,
$_FloatSafeNum
],
)->create_child_type(
name
=>
'RealSafeNum'
,
library
=> __PACKAGE__,
inlined
=>
sub
{
my
(
$self
,
$val
) =
@_
;
return
(
$_NumLike
->inline_check(
$val
),
"( !ref($val) && "
.
$_NumRange_perlsafe
->inline_check(
$val
).
" && not ("
.
"Data::Float::float_is_nan($val) || Data::Float::float_is_infinite($val))"
.
') || ('
.
Types::Standard::Object->inline_check(
$val
).
" && Scalar::Util::blessed($val)->new(1.2) == 1.2 && "
.
"not ($val->can('is_nan') && $val->is_nan || $val->can('is_inf') && ($val->is_inf('+') || $val->is_inf('-')) )"
.
')'
);
},
) );
#pod =head3 FixedBinary[`b, `s]
#pod
#pod A fixed-point number, represented as a C<`b> bit integer than has been shifted by C<`s> digits. For example, a
#pod C<FixedBinary[32, 4]> has a max of C<2**31-1 / 10**4 = 214748.3647>. Because integers do not hold NaN/Inf, this type fails
#pod on those.
#pod
#pod Otherwise, it has the same properties and caveats as the parameterized C<Float*> types.
#pod
#pod =cut
$meta
->add_type(
name
=>
'FixedBinary'
,
parent
=>
$_RealSafeNum
,
library
=> __PACKAGE__,
# kinda pointless without the parameters
constraint_generator
=>
sub
{
my
$self
=
$Type::Tiny::parameterize_type
;
my
(
$bits
,
$scale
) = (
shift
,
shift
);
$bits
=~ /\A[0-9]+\z/ or _croak(
"First parameter to FixedBinary[`b, `s] expected to be a positive integer; got $bits"
);
$scale
=~ /\A[0-9]+\z/ or _croak(
"Second parameter to FixedBinary[`b, `s] expected to be a positive integer; got $scale"
);
my
$sbits
=
$bits
- 1;
# So, we have a base-10 scale and a base-2 set of $bits. Lovely.
# We can't actually figure out if it's Perl safe until we find the
# $max, adjust with the $scale, and then go BACK to base-2 limits.
my
$div
=
$bigten
->copy->bpow(
$scale
);
my
(
$neg
,
$pos
) = (
# bdiv returns (quo,rem) in list context :/
scalar
$bigtwo
->copy->bpow(
$sbits
)->bmul(-1)->bdiv(
$div
),
scalar
$bigtwo
->copy->bpow(
$sbits
)->bsub(1)->bdiv(
$div
),
);
my
$digits
= ceil(
$sbits
* _BASE2_LOG );
my
$emin2
= ceil(
$scale
/ _BASE2_LOG );
my
$is_perl_safe
= (
Data::Float::significand_bits >=
$sbits
&&
Data::Float::min_finite_exp <= -
$emin2
);
my
$_NumRange_param
=
$_NumRange
->parameterize(
$neg
,
$pos
);
Type::Tiny->new(
display_name
=>
"FixedBinary[$bits, $scale]"
,
parent
=>
$self
,
library
=> __PACKAGE__,
constraint
=> __real_constraint_generator(
$is_perl_safe
,
$digits
,
$_NumRange_param
, 1),
inlined
=> __real_inline_generator (
$is_perl_safe
,
$digits
,
$_NumRange_param
, 1),
);
},
);
#pod =head3 FixedDecimal[`d, `s]
#pod
#pod Like L</FixedBinary[`b, `s]>, but for a C<`d> digit integer. Or, you could think of C<`d> and C<`s> as accuracy (significant
#pod figures) and decimal precision, respectively.
#pod
#pod =cut
$meta
->add_type(
name
=>
'FixedDecimal'
,
parent
=>
$_RealSafeNum
,
library
=> __PACKAGE__,
# kinda pointless without the parameters
constraint_generator
=>
sub
{
my
$self
=
$Type::Tiny::parameterize_type
;
my
(
$digits
,
$scale
) = (
shift
,
shift
);
$digits
=~ /\A[0-9]+\z/ or _croak(
"First parameter to FixedDecimal[`d, `s] expected to be a positive integer; got $digits"
);
$scale
=~ /\A[0-9]+\z/ or _croak(
"Second parameter to FixedDecimal[`d, `s] expected to be a positive integer; got $scale"
);
my
$sbits
= ceil(
$digits
/ _BASE2_LOG );
my
$emin2
= ceil(
$scale
/ _BASE2_LOG );
my
$is_perl_safe
= (
Data::Float::significand_bits >=
$sbits
&&
Data::Float::min_finite_exp <= -
$emin2
);
my
$div
=
$bigten
->copy->bpow(
$scale
);
my
$max
=
$bigten
->copy->bpow(
$digits
)->bsub(1)->bdiv(
$div
);
my
$_NumRange_param
=
$_NumRange
->parameterize(-
$max
,
$max
);
Type::Tiny->new(
display_name
=>
"FixedDecimal[$digits, $scale]"
,
parent
=>
$self
,
library
=> __PACKAGE__,
constraint
=> __real_constraint_generator(
$is_perl_safe
,
$digits
,
$_NumRange_param
, 1),
inlined
=> __real_inline_generator (
$is_perl_safe
,
$digits
,
$_NumRange_param
, 1),
);
},
);
#############################################################################
# Character types
#pod =head2 Characters
#pod
#pod Characters are basically encoded numbers, so there's a few types here. If you need types that handle multi-length strings, you're
#pod better off using L<Types::Encodings>.
#pod
#pod =head3 Char
#pod
#pod A single character. Unicode is supported, but it must be decoded first. A multi-byte character that Perl thinks is two separate
#pod characters will fail this type.
#pod
#pod =head3 Char[`b]
#pod
#pod A single character that fits within C<`b> bits. Unicode is supported, but it must be decoded first.
#pod
#pod =cut
$meta
->add_type(
name
=>
'Char'
,
parent
=> Types::Standard::Str,
library
=> __PACKAGE__,
constraint
=>
sub
{
length
(
$_
) == 1 },
# length() will do a proper Unicode char length
inlined
=>
sub
{
my
(
$self
,
$val
) =
@_
;
undef
,
"length($val) == 1"
;
},
constraint_generator
=>
sub
{
my
$self
=
$Type::Tiny::parameterize_type
;
my
(
$bits
) = (
shift
);
$bits
=~ /\A[0-9]+\z/ or _croak(
"Parameter to Char[`b] expected to be a positive integer; got $bits"
);
Type::Tiny->new(
display_name
=>
"Char[$bits]"
,
parent
=>
$self
,
library
=> __PACKAGE__,
constraint
=>
sub
{
ord
(
$_
) < 2*
*$bits
},
inlined
=>
sub
{
my
$val
=
$_
[1];
(
undef
,
"ord($val) < 2**$bits"
);
},
);
},
);
#############################################################################
# Types from Types::Common::Numeric
#pod =head2 Types::Common::Numeric analogues
#pod
#pod The L<Types::Common::Numeric> module has a lot of useful types, but none of them are compatible with blessed numbers. This module
#pod re-implements them to be grandchildren of L</NumLike> and L</IntLike>, which allows blessed numbers.
#pod
#pod Furthermore, the L</NumRange> and L</IntRange> checks are already implemented and described above.
#pod
#pod =head3 PositiveNum
#pod
#pod Accepts non-zero numbers in the positive range.
#pod
#pod =cut
$meta
->add_type(
name
=>
'PositiveNum'
,
parent
=>
$_NumRange
->parameterize(0,
undef
, 1),
message
=>
sub
{
"Must be a positive number"
},
);
#pod =head3 PositiveOrZeroNum
#pod
#pod Accepts numbers in the positive range, or zero.
#pod
#pod =cut
$meta
->add_type(
name
=>
'PositiveOrZeroNum'
,
parent
=>
$_NumRange
->parameterize(0),
message
=>
sub
{
"Must be a number greater than or equal to zero"
},
);
#pod =head3 PositiveInt
#pod
#pod Accepts non-zero integers in the positive range.
#pod
#pod =cut
$meta
->add_type(
name
=>
'PositiveInt'
,
parent
=>
$_IntRange
->parameterize(1),
message
=>
sub
{
"Must be a positive integer"
},
);
#pod =head3 PositiveOrZeroInt
#pod
#pod Accepts integers in the positive range, or zero.
#pod
#pod =cut
$meta
->add_type(
name
=>
'PositiveOrZeroInt'
,
parent
=>
$_IntRange
->parameterize(0),
message
=>
sub
{
"Must be an integer greater than or equal to zero"
},
);
#pod =head3 NegativeNum
#pod
#pod Accepts non-zero numbers in the negative range.
#pod
#pod =cut
$meta
->add_type(
name
=>
'NegativeNum'
,
parent
=>
$_NumRange
->parameterize(
undef
, 0,
undef
, 1),
message
=>
sub
{
"Must be a negative number"
},
);
#pod =head3 NegativeOrZeroNum
#pod
#pod Accepts numbers in the negative range, or zero.
#pod
#pod =cut
$meta
->add_type(
name
=>
'NegativeOrZeroNum'
,
parent
=>
$_NumRange
->parameterize(
undef
, 0),
message
=>
sub
{
"Must be a number less than or equal to zero"
},
);
#pod =head3 NegativeInt
#pod
#pod Accepts non-zero integers in the negative range.
#pod
#pod =cut
$meta
->add_type(
name
=>
'NegativeInt'
,
parent
=>
$_IntRange
->parameterize(
undef
, -1),
message
=>
sub
{
"Must be a negative integer"
},
);
#pod =head3 NegativeOrZeroInt
#pod
#pod Accepts integers in the negative range, or zero.
#pod
#pod =cut
$meta
->add_type(
name
=>
'NegativeOrZeroInt'
,
parent
=>
$_IntRange
->parameterize(
undef
, 0),
message
=>
sub
{
"Must be an integer less than or equal to zero"
},
);
#pod =head3 SingleDigit
#pod
#pod Accepts integers between -9 and 9.
#pod
#pod =cut
$meta
->add_type(
name
=>
'SingleDigit'
,
parent
=>
$_IntRange
->parameterize(-9, 9),
message
=>
sub
{
"Must be a single digit"
},
);
42;
__END__
=pod
=encoding UTF-8
=head1 NAME
Types::Numbers - Type constraints for numbers
=head1 VERSION
version v1.0.1
=head1 DESCRIPTION
Because we deal with numbers every day in our programs and modules, this is an extensive
L<Type::Tiny> library of number validations. Like L<Type::Tiny>, these types work with
all modern OO platforms and as a standalone type system.
=head1 TYPES
=head2 Overview
All of these types strive for the accurate storage and validation of many different types of
numbers, including some storage types that Perl doesn't natively support.
The hierarchy of the types is as follows:
(T:S = From Types::Standard)
(~T:C:N = Based on Types::Common::Numeric types)
Item (T:S)
Defined (T:S)
NumLike
NumRange[`n, `p] (~T:C:N)
PositiveNum (~T:C:N)
PositiveOrZeroNum (~T:C:N)
NegativeNum (~T:C:N)
NegativeOrZeroNum (~T:C:N)
IntLike
SignedInt[`b]
UnsignedInt[`b]
IntRange[`n, `p] (~T:C:N)
PositiveInt (~T:C:N)
PositiveOrZeroInt (~T:C:N)
NegativeInt (~T:C:N)
NegativeOrZeroInt (~T:C:N)
SingleDigit (~T:C:N)
PerlNum
PerlSafeInt
PerlSafeFloat
BlessedNum[`d]
BlessedInt[`d]
BlessedFloat[`d]
NaN
Inf[`s]
FloatSafeNum
FloatBinary[`b, `e]
FloatDecimal[`d, `e]
RealNum
RealSafeNum
FixedBinary[`b, `s]
FixedDecimal[`d, `s]
Value (T:S)
Str (T:S)
Char[`b]
=head2 Basic types
=head3 NumLike
Behaves like C<LaxNum> from L<Types::Standard>, but will also accept blessed number types. Unlike
C<StrictNum>, it will accept C<NaN> and C<Inf> numbers.
=head3 NumRange[`n, `p]
Only accepts numbers within a certain range. By default, the two parameters are the minimums and maximums,
inclusive. However, this type is also compatible with a few different parameter styles, a la L<Types::Common::Numeric>.
The minimum/maximums can be omitted or undefined. Or two extra boolean parameters can be added to specify exclusivity:
NumRange[0.1, 10.0, 0, 0] # both inclusive
NumRange[0.1, 10.0, 0, 1] # exclusive maximum, so 10.0 is invalid
NumRange[0.1, 10.0, 1, 0] # exclusive minimum, so 0.1 is invalid
NumRange[0.1, 10.0, 1, 1] # both exclusive
NumRange[0.1] # lower bound check only
NumRange[undef, 10.0] # upper bound check only
NumRange[0.1, undef, 1] # lower bound check only, exclusively
NumRange[undef, 10.0, 1, 1] # upper bound check only, exclusively (third param ignored)
=head3 PerlNum
Exactly like C<LaxNum>, but with a different parent. Only accepts unblessed numbers.
=head3 BlessedNum
Only accepts blessed numbers. A blessed number would be using something like L<Math::BigInt> or
L<Math::BigFloat>. It doesn't directly C<isa> check those classes, just that the number is
blessed.
=head3 BlessedNum[`d]
A blessed number that supports at least certain amount of digit accuracy. The blessed number must
support the C<accuracy> or C<div_scale> method.
For example, C<BlessedNum[40]> would work for the default settings of L<Math::BigInt>, and supports
numbers at least as big as 128-bit integers.
=head3 NaN
A "not-a-number" value, either embedded into the Perl native float or a blessed C<NaN>,
checked via C<is_nan>.
=head3 Inf
An infinity value, either embedded into the Perl native float or a blessed C<Inf>, checked via
C<is_inf>.
=head3 Inf[`s]
Inf['+']
Inf['-']
An infinity value with a certain sign, either embedded into the Perl native float or a blessed
C<Inf>, checked via C<is_inf>. The parameter must be a plus or minus character.
=head3 RealNum
Like L</NumLike>, but does not accept NaN or Inf. Closer to the spirit of C<StrictNum>, but
accepts blessed numbers as well.
=head2 Integers
=head3 IntLike
Behaves like C<Int> from L<Types::Standard>, but will also accept blessed number types and integers
in E notation. There are no expectations of storage limitations here. (See L</SignedInt> for
that.)
=head3 IntRange[`n, `p]
Only accepts integers within a certain range. By default, the two parameters are the minimums and maximums,
inclusive. Though, the minimum/maximums can be omitted or undefined.
=head3 PerlSafeInt
A Perl (unblessed) integer number than can safely hold the integer presented. This varies between
32-bit and 64-bit versions of Perl.
For example, for most 32-bit versions of Perl, the largest integer than can be safely held in a
4-byte NV (floating point number) is C<9007199254740992>. Numbers can go higher than that, but due
to the NV's mantissa length (accuracy), information is lost beyond this point.
In this case, C<...992> would pass and C<...993> would fail.
(Technically, the max integer is C<...993>, but we can't tell the difference between C<...993> and
C<...994>, so the cut off point is C<...992>, inclusive.)
Be aware that Perls compiled with "long doubles" have a unique problem with storage and information
loss: their number form maintains accuracy while their (default) stringified form loses
information. For example, take the max safe integer for a long double:
$num = 18446744073709551615;
say $num; # 1.84467440737095516e+19
say $num == 18446744073709551615; # true, so the full number is still there
say sprintf('%u', $num); # 18446744073709551615
These numbers are considered safe for storage. If this is not preferred, consider a simple C</e/>
check for stringified E notation.
=head3 BlessedInt
A blessed number than is holding an integer. (A L<Math::BigFloat> with an integer value would
still pass.)
=head3 BlessedInt[`d]
A blessed number holding an integer of at most C<`d> digits (inclusive). The blessed number
container must also have digit accuracy to support this number. (See L</BlessedNum[`d]>.)
=head3 SignedInt
A signed integer (blessed or otherwise) that can safely hold its own number. This is different
than L</IntLike>, which doesn't check for storage limitations.
=head3 SignedInt[`b]
A signed integer that can hold a C<`b> bit number and is within those boundaries. One bit is
reserved for the sign, so the max limit on a 32-bit integer is actually C<2**31-1> or
C<2147483647>.
=head3 UnsignedInt
Like L</SignedInt>, but with a minimum boundary of zero.
=head3 UnsignedInt[`b]
Like L</SignedInt[`b]>, but for unsigned integers. Also, unsigned integers gain their extra bit,
so the maximum is twice as high.
=head2 Floating-point numbers
=head3 PerlSafeFloat
A Perl native float that is in the "integer safe" range, or is a NaN/Inf value.
This doesn't guarantee that every single fractional number is going to retain all of its
information here. It only guarantees that the whole number will be retained, even if the
fractional part is partly or completely lost.
=head3 BlessedFloat
A blessed number that will support fractional numbers. A L<Math::BigFloat> number will pass,
whereas a L<Math::BigInt> number will fail. However, if that L<Math::BigInt> number is capable of
upgrading to a L<Math::BigFloat>, it will pass.
=head3 BlessedFloat[`d]
A float-capable blessed number that supports at least certain amount of digit accuracy. The number
itself is not boundary checked, as it is excessively difficult to figure out the exact dimensions
of a floating point number. It would also not be useful for numbers like C<0.333333...> to fail
checks.
=head3 FloatSafeNum
A Union of L</PerlSafeFloat> and L</BlessedFloat>. In other words, a float-capable number with
some basic checks to make sure information is retained.
=head3 FloatBinary[`b, `e]
A floating-point number that can hold a C<`b> bit number with C<`e> bits of exponent, and is within
those boundaries (or is NaN/Inf). The bit breakdown follows traditional IEEE 754 floating point
standards. For example:
FloatBinary[32, 8] =
32 bits total (`b)
23 bit mantissa (significand precision)
8 bit exponent (`e)
1 bit sign (+/-)
Unlike the C<*Int> types, if Perl's native NV cannot support all dimensions of the floating-point
number without losing information, then unblessed numbers are completely off the table. For
example, assuming a 32-bit machine:
(UnsignedInt[64])->check( 0 ) # pass
(UnsignedInt[64])->check( 2 ** 30 ) # pass
(UnsignedInt[64])->check( 2 ** 60 ) # fail, because 32-bit NVs can't safely hold it
(FloatBinary[64, 11])->check( 0 ) # fail
(FloatBinary[64, 11])->check( $any_unblessed_number ) # fail
=head3 FloatDecimal[`d, `e]
A floating-point number that can hold a C<`d> digit number with C<`e> digits of exponent. Modeled
after the IEEE 754 "decimal" float. Rejects all Perl NVs that won't support the dimensions. (See
L</FloatBinary[`b, `e]>.)
=head2 Fixed-point numbers
=head3 RealSafeNum
Like L</FloatSafeNum>, but rejects any NaN/Inf.
=head3 FixedBinary[`b, `s]
A fixed-point number, represented as a C<`b> bit integer than has been shifted by C<`s> digits. For example, a
C<FixedBinary[32, 4]> has a max of C<2**31-1 / 10**4 = 214748.3647>. Because integers do not hold NaN/Inf, this type fails
on those.
Otherwise, it has the same properties and caveats as the parameterized C<Float*> types.
=head3 FixedDecimal[`d, `s]
Like L</FixedBinary[`b, `s]>, but for a C<`d> digit integer. Or, you could think of C<`d> and C<`s> as accuracy (significant
figures) and decimal precision, respectively.
=head2 Characters
Characters are basically encoded numbers, so there's a few types here. If you need types that handle multi-length strings, you're
better off using L<Types::Encodings>.
=head3 Char
A single character. Unicode is supported, but it must be decoded first. A multi-byte character that Perl thinks is two separate
characters will fail this type.
=head3 Char[`b]
A single character that fits within C<`b> bits. Unicode is supported, but it must be decoded first.
=head2 Types::Common::Numeric analogues
The L<Types::Common::Numeric> module has a lot of useful types, but none of them are compatible with blessed numbers. This module
re-implements them to be grandchildren of L</NumLike> and L</IntLike>, which allows blessed numbers.
Furthermore, the L</NumRange> and L</IntRange> checks are already implemented and described above.
=head3 PositiveNum
Accepts non-zero numbers in the positive range.
=head3 PositiveOrZeroNum
Accepts numbers in the positive range, or zero.
=head3 PositiveInt
Accepts non-zero integers in the positive range.
=head3 PositiveOrZeroInt
Accepts integers in the positive range, or zero.
=head3 NegativeNum
Accepts non-zero numbers in the negative range.
=head3 NegativeOrZeroNum
Accepts numbers in the negative range, or zero.
=head3 NegativeInt
Accepts non-zero integers in the negative range.
=head3 NegativeOrZeroInt
Accepts integers in the negative range, or zero.
=head3 SingleDigit
Accepts integers between -9 and 9.
=head1 AUTHOR
Grant Street Group <developers@grantstreet.com>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2013 - 2022 by Grant Street Group.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
=cut