STRANGE LOOP 2012
Spire
Better Numerics for Scala
Erik Osheim (@d6) and Tom Switzer (@tixxit)
What is Spire?
A numeric library for Scala.
Intended to be generic, fast, and precise.
Motivations
Generic numeric programming?
// direct def gcd(a: Int, b: Int): Int = if (a % b == 0) b else gcd(b, a % b) // scala generics def gcd[A: Integral](a: A, b: A): A = if (a % b == implicitly[Integral[A]].zero) b else gcd(b, a % b) // spire! def gcd[@spec A: Integral](a: A, b: A): A = if (a % b === Integral[A].zero) b else gcd(b, a % b)
How do Scala's generics perform?
Benchmarks using Caliper with 1M iterations/elements
slowdown vs direct code | ||
benchmark | scala.math | spire.math |
pairwise add | 14.5 | 1.0 |
increment | 18'674'145.0(*) | 1.0 |
min/max | 4.8 | 1.0 |
gcd | 1.6 | 1.0 |
scale | 4.9 | 1.0 |
* This is not a typo!
Separating precision from algorithm
...for when you can't employ a team of numerical analysts!
Separating precision from algorithm
...for when you can't employ a team of numerical analysts!
def mean[@spec A: Fractional](ns: Array[A]) = ns.qsum / Fractional[A].fromInt(ns.size)
Separating precision from algorithm
...for when you can't employ a team of numerical analysts!
def mean[@spec A: Fractional](ns: Array[A]) = ns.qsum / Fractional[A].fromInt(ns.size) scala> mean[Double](Array(1e20, 1, -1e20)) res0: 0.0 scala> mean[BigDecimal](Array(1e20, 1, -1e20)) res1: BigDecimal(0.333...)
More precision problems
scala> 363.sqrt - 300.sqrt - 3.sqrt
More precision problems
scala> 363.sqrt - 300.sqrt - 3.sqrt Double = -2.220446049250313E-15
More precision problems
scala> 363.sqrt - 300.sqrt - 3.sqrt Double = -2.220446049250313E-15 BigDecimal = -8E-33 // default precision
More precision problems
scala> 363.sqrt - 300.sqrt - 3.sqrt Double = -2.220446049250313E-15 BigDecimal = -8E-33 // default precision -6E-99 // 100 digits precision 2E-99 // 1'000 digits precision -2E-9999 // 10'000 digits precision
Esoteric bullshit
// ok (33.2).floor // missing BigDecimal(33.0).floor
Esoteric bullshit
// ok (33.6).round // won't compile BigDecimal(33.6).round // blech BigDecimal(33.6).round(new java.lang.MathContext(128)) // oops 5e100.round -> 9223372036854775807L
Esoteric bullshit
// great BigDecimal(2.2) pow 3 // ugly math.pow(2.0, 3.0) // these aren't the longs you're looking for math.pow(19L, 3L) -> 6859.0 // too bad BigDecimal(3.1) pow 2.5 // too bad
Esoteric bullshit
// ugly but useful math.cbrt(12345.0) math.sqrt(12345.0) // nope BigDecimal(12345.0).cbrt math.cbrt(BigDecimal(12345.0)) // sorry BigDecimal(12345.0).sqrt math.sqrt(BigDecimal(12345.0))
Esoteric bullshit
// ugly but works math.log(54321.0) // nope, fail BigDecimal(54321).log math.log(BigDecimal(54321)) // don't even think about trigonometry!
Implementation
Typeclasses
Modular, ad-hoc polymorphism, including over primitive types.
Encoded using Scala's system of implicits.
Specialization + Macros
Scala can classes and methods on generic type parameters.
Like C++ templates but at definition site, and other differences.
Specialization + Macros
def mySum[@spec A:Ring](ns: List[A], total:A): A = ns match { case n :: ns => mySum(ns, total + n) case Nil => total } mySum[Int](ns, 0)
Specialization + Macros
def mySum$mIc$sp(ns: List[Int], total:Int) (implicit ev:Ring$mcI$sp): Int = ns match { case n :: ns => mySum(ns, new RingOps(total)(ev).+(n)) case Nil => total } mySum$mIc$sp(ns, 0)(Ring.IntIsRing)
Specialization + Macros
def mySum$mIc$sp(ns: List[Int], total:Int) (implicit ev:Ring$mcI$sp): Int = ns match { case n :: ns => mySum(ns, ev.plus(total, n)) case Nil => total } mySum$mIc$sp(ns, 0)(Ring.IntIsRing)
Rational
Exact (closed) for all field operations.
Rational
Exact (closed) for all field operations.
scala> mean[Rational](List(1e20, 1, -1e20)) res0: spire.math.Rational = 1/3
Rational
Why implement another rational type in Java?
Because Aprational is sloooooooow!
Summing 100 rationals is 5'148x faster in Spire than Aprational.
Rational
Rational's SafeLong is a good compromise of speed + precision.
Real
Lazily-computed number type for arbitrary precision.
Exact(*) for fields and k-roots...
Real
Lazily-computed number type for arbitrary precision.
Exact(*) for fields and k-roots...
scala> Real(363).sqrt - Real(300).sqrt - Real(3).sqrt res0: spire.math.Real = 0
Real
def distance(x: Real, y: Real): Real = ((x ** 2) + (y ** 2)).sqrt // rational literals using macros! val d = distance(r"33/2", r"16/3") // rough approximation val approx = d +/- 1e-10 // finer approximation using default math context val huge = d.toBigDecimal
Real
- On Guaranteed Accuracy Computation.
C. K. Yap. - A Separation Bound for Real Algebraic Expressions.
C. Burnikel, et al. - A New Constructive Root Bound for Algebraic Expressions.
C. Li and C. Yap.
More types!
- Complex[A]
- Interval[A]
- boxed Number
More features!
- Literal number syntax macros
- Specialized sorting, summation, etc
- Optimized cfor macro
- Precise pow for Long
- Trig, log, k-roots for BigDecimal(*)
Get Involved!
MIT license
Code available at Github.
Release coming soon for Scala 2.10.0!
"Now with 100% more homepage!"