Skip to content

hammerlab/math-utils

Repository files navigation

math-utils

Build Status codecov

Math, stats, and miscellaneous numeric and type-related utilities:

  • format: format numbers to a given number of significant figures
  • stats: collect+display statistics about collections of numeric elements
  • tolerance: "fuzzy-equality" for Doubles
  • types: misc type- and type-class-utilities (auto-derived Orderings and Monoids, a scala-js runtime-predicate, etc.)
  • utils: misc numeric utilities (binomial coefficients, interpolation, etc.)
  • cubic/quartic: solve cubic/quartic equations in terms of Spire type-classes

  Maven Central: org.hammerlab.math:::format:1.1.1

Format numbers to a given number of significant figures:

import hammerlab.math.sigfigs._
implicit val sf: SigFigs = 3

import cats.syntax.show._

1.2345.show
// 1.23

123.45.show
// 123

123456.0.show
// 123456; same length as "1.23e5", so don't bother abbreviating to scientific notation

1234567.0.show
// 1.23e6

0.0000123456.show
// 1.23e-5

  Maven Central: org.hammerlab.math:::stats:1.3.3

org.hammerlab.stats.Stats has APIs for ingesting numeric elements and outputting nicely formatted statistics about them; modeled after Apache commons-math DescriptiveStatistics:

// Context for displaying ints, longs, and doubles
import cats.syntax.show._
import cats.instances.int.catsStdShowForInt
import cats.instances.long.catsStdShowForLong
import hammerlab.math.sigfigs._
implicit val sf: SigFigs = 2

// Stats are displayed on multiple lines
import hammerlab.lines._
import hammerlab.indent.spaces

// Simulate some doubles to collect statistics about
import util.Random._; setSeed(123)
val samples = Array.fill(100)(nextGaussian)

import hammerlab.stats._

// Create and display a Stats:
val stats = Stats(samples)
stats.showLines
// N: 100, μ/σ: -0.02/1, med/mad: -0.038/0.71
//  elems: -1.4 0.63 0.23 0.28 0.18 -0.37 1.4 0.36 -0.21 1 … -0.4 -1.6 -0.11 -1.1 -0.39 1.5 -0.17 1.6 0.25 -0.25
// sorted: -3.1 -2.2 -1.9 -1.9 -1.7 -1.6 -1.4 -1.4 -1.4 -1.4 … 1.3 1.4 1.4 1.5 1.5 1.5 1.6 1.7 1.8 2.8
//   .01:	-3.1
//   .05:	-1.7
//   .10:	-1.4
//   .25:	-0.75
//   .50:	-0.038
//   .75:	0.67
//   .90:	1.3
//   .95:	1.5
//   .99:	2.7

As a bonus, it can ingest numbers in histogram-style / run-length-encoded format, supporting Long values as well for computations involving element counts from RDDs:

// Create a Stats from a histogram:   
Stats.fromHist(
  List(
    1  10000000000L,
    2   1000000000L,
    1          100L,
    2   1000000000L
  )
)
.showLines
// N: 12000000100, μ/σ: 1.2/0.37, med/mad: 1/0
//  elems: 1×10000000000 2×1000000000 1×100 2×1000000000
// sorted: 1×10000000100 2×2000000000
//   .75:	1
//   .90:	2

  Maven Central: org.hammerlab.math:::tolerance:1.0.1

Fuzzy-equality for Doubles:

import hammerlab.math.tolerance._

// let things be "equal" that are within 1% of one another
implicit val ε: E = 1e-2
2.0  === 2.02  //  true
2.02 === 2.04  //  true
2.0  === 2.03  // false!

hammerlab/test-utils Suite interfaces come with a (configurable) 1e-6 fuzz-factor for Double comparisons

  Maven Central: org.hammerlab:::types:1.5.0

either/or

Shorthands for Either and "or" (cats.Ior) subclasses:

import hammerlab.either._
import hammerlab.or._

collection

CanBuildFrom instances for Arrays and Vectors:

import hammerlab.collection._

monoid

cats.Monoid derivations for products and coproducts:

import hammerlab.monoid._
case class Samples(num: Int, sum: Long)

Samples(10, 1000) |+| Samples(20, 2000)
// Samples(30, 3000)

option

Some Option-related helpers:

Opt

Wrapper class for Option that implicitly wraps values in a Some where necessary:

import hammerlab.option._

// remove default-argument boilerplate
def foo(n: Opt[Int] = 111) = { ??? }

// remove call-site boilerplate
foo(333)

// degrade gracefully to regular Option
foo(Some(222))
foo(None)

? operator

Create a Some iff a predicate is true:

(20 > 10) ? "abc"
// Some(abc)

(20 < 10) ? "abc"
// None

scalajs

Predicates for specializing behavior on the JVM vs JS:

import hammerlab.scalajs._
js (111)(222)  // 111 in JS, 222 in JVM
JS (111)(222)  // same
222 js_? 111   // same 

jvm(111)(222)  // 111 in JVM, 222 in JS
JVM(111)(222)  // same
222 jvm_? 111  // same

str

Name (alias Str) type that is implicitly-constructible from a String or Symbol:

import hammerlab.str._
val a: Str = "abc"
val b: Str = 'def

  Maven Central: org.hammerlab.math:::utils:2.4.0

Miscellaneous numeric utilities

import hammerlab.math._

min

Overloads of math.min that take an Int and a Long and return an `Int

HyperGeometric Distribution

hammerlab.math.utils.HypergeometricDistribution is an implementation of a hypergeometric distribution, modeled after org.apache.commons.math3.distribution.HypergeometricDistribution, but supporting 8-byte Long parameters.

div

Up-rounding integer-division:

div(20 , 10)  // 2
div(21L, 10)  // 3

Steps

geometricSteps(100, 15)
// SortedSet(0, 1, 2, 3, 4, 5, 6, 8, 11, 17, 24, 34, 49, 70, 100)

roundNumbers(100)
// SortedSet(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100)

  Maven Central: org.hammerlab.math:::cubic:1.0.0   Maven Central: org.hammerlab.math:::quartic:1.0.0

Solve cubics with Double coefficients (and Complex[Double] roots):

import cubic.complex._
import spire.implicits._

val dbl = Cubic.doubleComplex[Double]

// Solutions to x³ + 2x² + 3x + 4 = 0:
dbl(1, 2, 3, 4)
// List(
//   -0.175 + 1.547i,
//   -1.651 - 4.457e-16i,
//   -0.175 - 1.54i
// )

// x³ + 3x² + 3x + 1 = 0:
dbl.monic(3, 3, 1)
List(
  -1.0 - 0.0i,
  -1.0 + 0.0i,
  -1.0 + 0.0i
)

// x³ + 4x + 6 = 0:
dbl.depressed(4, 6)
// List(
//   0.567 + 2.228i,
//   -1.135 + -6.594e-16i,
//   0.567 + -2.228i
// )

// Solve cubics with BigDecimal coefficients (and Complex[BigDecimal] roots):

val bigdbl = Cubic.doubleComplex[BigDecimal]

bigdbl(1, 2, 3, 4)
List(
  -0.17468540428030620349438212279655060000000000000000000000000000000000000 + 1.546868887231396634770605370742269000000000000000000000000000000000000000i, 
  -1.650629191439388248503394865883312800000000000000000000000000000000000000000000000000 + -3.43495611274383380622701277675890600000000000000000000000E-16i,
  -0.17468540428030587574830256705833460000000000000000000000000000000000000 + -1.546868887231396291274994096359002000000000000000000000000000000000000000i
)

Quartics work similarly:

import quartic.complex._

// Solve quartics with Double coefficients
val dbl = Quartic.doubleComplex[Double]

// Solve quartics with BigDouble coefficients
val bigdbl = Quartic.doubleComplex[BigDecimal]

dbl(a, b, c, d, e)
dbl.monic(b, c, d, e)
dbl.depressed(p, q, r)