Perl 6 Numerics
Solomon Foster <colomon@gmail.com>
#perl6
Perl 6 Numerics
- Powerful
- Takes advantage of both classes and roles
- Extensible
- "There's more than one way to do it"
Numeric Roles
- The two core roles are
Numeric
and Real
- Numeric represents any scalar number
- Real represents any numeric type which is a subset of the real numbers
- Examples include
Int
, Num
, and Rat
-
Real
does Numeric
(ie all Real
s are also Numeric
)
What is Numeric but not Real?
- Complex numbers are the canonical example
- Util has implemented Quaternions in a module
What math types aren't Numeric?
- Math types which do not have a solid definition for the basic operators
- For instance, a 3D vector type probably isn't
Numeric
- It has addition and subtraction
- It doesn't have multiplication or division with other vectors
- These boundaries are still a bit fuzzy
So, Real first
- If you want to handle things on the real number line
- And you don't care about the particular type
- The
Real
role provides a full suite of standard math operations
- Arithmetic operators:
-
(prefix) +
-
*
/
%
**
- Comparison operators:
cmp
<=>
==
!=
<
>
<=
>=
Real Methods
- Basic methods:
abs
, exp
, log
, sqrt
, roots
, sign
, floor
, ceiling
, truncate
, round
, cis
, unpolar
, rand
- Trig methods:
sin
, asin
, cos
, acos
, tan
, atan
, sec
, asec
, cosec
, acosec
, cotan
, acotan
, atan2
- Hyperbolic trig methods:
sinh
, asinh
, cosh
, acosh
, tanh
, atanh
, sech
, asech
, cosech
, acosech
, cotanh
, acotanh
- Coercion and test methods:
Real
, Bool
, Int
, Rat
, Num
, Complex
, Str
, Bridge
, reals
, isNaN
Gotchas
- These all work for every
Real
type
- But their result may be a different
Real
type
- Consider
10.sqrt
-
10
is an Int
- The result (
3.16227766016838
) is a Num
- That's true for
9.sqrt
as well, even though 3
could be an Int
Also...
- You might lose precision this way
-
(10 ** 1000).sqrt
is not guaranteed to be exactly 10 ** 500
- This is under-specified and may change
Num
- Your basic machine floating-point double
- Boring and useful
Int
- Infinite precision integer type
- Big numbers NYI in Rakudo, work in Niecza
- Includes
div
, mod
, gcd
, and lcm
operators
- If you use
/
on two Int
s, the result is a Rat
- In theory, also has values
-Inf
and +Inf
int
- Spec defines
int
and various intNNN
types
- Native integers (NNN bits)
- NYI anywhere, maybe in the next generation of Rakudo
Rat
- Perl 6's default
Rational
type
- If possible, a
Rat
is constructed when you use Int / Int
- If possible?
- By spec, numerator is an
Int
, denominator an uint64
- The denominator is finite to avoid situations where it explodes in size
- So if your
Int / Int
cannot fit it in the representation, it must create something else
Rat II
- Degrades to either
Num
or a lesser precision Rat
- In practice, both Rakudo and Niecza choose
Num
- I'd kind of like to change the spec to match that
- If you want extended precision rationals, you need to ask for a
FatRat
explicitly
- In practice in Rakudo,
Rat
is (finite) Int
over (finite) Int
Rat III
- The default format for literal decimal numbers in Perl 6
- That is to say,
9.45
(eg) is a Rat
- To get a
Num
, you need to say 9.45e0
Rat IIII
for 0.0, 0.1 ... 1.0 -> $x {
say $x;
}
loop ($x = 0.0; $x <= 1.0; $x += 0.1) {
say $x;
}
Rat V
- Suppose
my Rat $x = 4 / 5
-
$x.Str
is 0.8
(NYI on Niecza)
-
$x.perl
is 4/5
Rat VI
- In theory, this does not reduce unless needed or you call
.perl
- In practice, both Rakudo and Niecza reduce fraction
- The spec here feels like a half-formed idea which isn't consistent
- I plan to change the spec on this when no one is watching
- I mean, when I figure out the right thing to change it to
FatRat
- A rational type which is a full (infinite)
Int
over another (infinite) Int
- Only implemented in Niecza so far
- For slower but exact math
- Though note that methods like
.sin
are likely to only have Num
precision
Rational
- The spec defines a
Rational
parametric role which both Rat
and FatRat
do
-
Rat
is Rational[Int,uint64]
, FatRat
Rational[Int,Int]
- Makes great sense -- most rational calculations don't care about precision internally
- Spec also mentions lowercase types, like
rat8
(which is Rational[int16,uint8]
)
- Nobody implements any of this yet
Complex
- Perl 6's basic complex number type is
Complex
- In Rakudo, it's two
Real
s
- In Niecza, it's two
Num
s
- The spec seems pretty vague on the matter
- The spec also mentions
complex
, which is presumably two num
s.
- Same methods that
Real
has, except a few that don't make sense
Trig functions
- Full suite of normal and hyperbolic functions
- Work on all built-in
Numeric
types
- In theory: Rakudo doesn't implement all the types and Niecza doesn't do Trig yet
- Actually properly supporting them on very large numbers is an interesting problem
- And I expect that
FatRat
support will lose precision by converting to Num
first
Tricky bits I
-
prefix:<->
(ie the negation operator) does not have as high a precedence as you might expect
- For instance,
-1.abs
is -1
- That's because it parses as
-(1.abs)
- Likewise,
-1 ** 2
is also -1
- It's
-(1 ** 2)
- Honest, that's how mathematicians do it!
Tricky bits II
- Math operations generally try to stay in the domain of the inputs
- For instance,
(-1).sqrt
is NaN
- That's because it's real square root
- If you want a complex square root, you have to have a
Complex
-
(-1).Complex.sqrt
is 0 + 1i
- Note that Niecza explicitly disavows this particular logic!
Tricky bits III
- The spec specifically forbids creating a
FatRat
unless requested
- This means that many operations which obviously could work won't
- For instance, in Niecza
1 / 10 ** 300 == 1E-300
(ie a Num
)
- And
1 / 10 ** 1000 == 0
(closest a Num
can come)
Tricky bits IIII
- On the other hand,
FatRat.new(1, 1) / 10 ** 1000
is exact
-
1.FatRat
should probably also work, but it is NYI
- The flip side of this is that
FatRat
is sticky
- Even if the result could be represented as a normal
Rat
, it won't be
- ... actually, a method for checking and doing that conversion seems like it might be useful
Making new Real types
- Perl 6 has a generous set of built-in math types
- But people who deal with numbers always want more
- The
Real
role is designed to make it almost trivially easy
Money
class Money does Real {
has $.cents;
multi method new(Real $dollars) {
self.bless(*, :cents(($dollars * 100).Int));
}
multi method new(Int :$cents) {
self.bless(*, :$cents);
}
method Bridge() { $.cents.Bridge / 100.Bridge; }
method perl() { "Money.new(cents => $.cents)"; }
method isNaN() { $.cents.isNaN; }
}
Money II
- That's a fully functional, if somewhat useless
Real
type
- All
Real
operators and methods work on it
- Problem is it's not closed
- Any math you do with it will leave the class
- ie
Money + Money ~~ Num
Money III
multi sub infix:<+>(Money $a, Money $b) {
Money.new(cents => $a.cents + $b.cents);
}
Money IIII
- With that,
Money + Money ~~ Money
- Obviously, implementing the operators is still work
- But you only need to implement those that you must
- Everything else comes for free
Bridge
- The magic comes from the Bridge method
- The question is, how can two
Real
types which don't know about each other interact?
- The answer is to convert to a common core Perl 6 type they both know about
- The tricky part is, what should that type be?
Bridge II
-
Num
is a sensible compromise for accuracy and speed
-
FatRat
might be the choice of those requiring great precision
- Using
.Bridge
means your code doesn't have to make that choice
- Any
Real
method or sub which isn't overloaded for your type will call .Bridge
- That returns a type the Perl 6 core does know about
Bridge III
- What particular type is returned isn't specified in the spec
- If you implement your
.Bridge
method in terms of simpler .Bridge
methods, you don't need to know the actual type
-
method Bridge() { $.cents.Bridge / 100.Bridge; }
for instance
What about new non-real numeric types?
- It's not clear to me how to have sensible default options for non-real types
- I added a method
.reals
with the idea that you convert your item to a series of Real
s
- Then it can be compared with any other
Numeric
type, lexicographically Real
-by-Real
- Matches Larry's goal that any two
Numeric
types should be comparable
- Lots of people seem to hate this
Numbers & the bigger world of Perl 6
- Most numeric operators use
prefix:<+>
to convert non-Numeric
variables to Numeric
- This is the equivalent of calling the
.Numeric
method on that variable
- So
+"10" == 10.Numeric == 10
- Note that it is
Numeric
and not Real
- So it will not magically remove the imaginary part from
Complex
numbers
- This can sometimes cause unexpected results
Examples
# Find the 201st Fibonacci number
niecza> my @fib := 1, 1, *+* ... *; say @fib[200];
453973694165307953197296969697410619233826
Examples
# Find smallest number divisible by 2..20
sub divides-by-all-up-to($a, $b) {
!(2..$b).grep($a !%% *);
}
my $N = [*] 2, 3, 5, 7, 11, 13, 17, 19;
my @attempts := $N, 2 * $N
...
{ divides-by-all-up-to($_, 20) };
say @attempts[*-1];
Examples
# Find smallest number divisible by 2..20
# sorear++ version
say [lcm] 2..20;
# answer is 232792560, btw
Examples
# inner core of a Mandelbrot set program
sub mandel(Complex $c) {
my $z = 0;
for ^$max_iterations {
$z = $z * $z + $c;
return True if $z.abs > 2;
}
return False;
}
Examples
POINT point = HwMakePoint (0.0, 0.0, 0.0);
double den = 0.0;
for (unsigned int i = 0; i <= degree; i++)
{
point += basis [i] * weights [span + i - degree] * points [span + i - degree];
den += basis [i] * weights [span + i - degree];
}
point = point / den;
Examples
# NURBS curve evaluation "loop" in p6
my $slice = span - degree .. span;
my @bw = basis[0 .. degree]
»*« weights[$slice];
my $point = ([+] @bw »*« points[$slice])
/ [+] @bw;
Talk Online
- Should be the top post on http://justrakudoit.wordpress.com for a few days
- Or feel free to ping me on the
#perl6
IRC channel on freenode
- Or e-mail me at colomon@gmail.com
- Or grab me in the hallway
- Thanks to Moritz Lenz for the presentation software
- Thanks to moritz, TimToady, and PerlJam for suggestions
- Thanks to you for listening!