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 Reals 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 Ints, 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
Reals
- In Niecza, it's two
Nums
- The spec seems pretty vague on the matter
- The spec also mentions
complex, which is presumably two nums.
- 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 Reals
- 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!