Raphael Roth
Raphael Roth

Reputation: 27373

Unable to write method in Scala working both for Double and Float

I'm having trouble to write a method which can be used with Float and Double. The problem is that I need to multiply my generic type parameter A with a Double inside my method. It boils down to something like this:

 def multiplyWithPi[A](in: A)(implicit num: Numeric[A]) : A = {
    Math.PI * in // does not compile
    num.times(Math.PI, in) // does not compile
    num.times(Math.PI.asInstanceOf[A],in) // does not work (class-cast exception)
  }

How can I do that? It's important that return type is the same as the input type

Upvotes: 3

Views: 191

Answers (3)

Rüdiger Klaehn
Rüdiger Klaehn

Reputation: 12565

The Numeric[T] typeclass that is built into scala is pretty basic. If you want to do something a bit more advanced with math in scala, I suggest using the spire math library.

Here is how you would implement the function in spire:

import spire.algebra._ // algebraic typeclasses
import spire.implicits._ // syntax and instances

def multiplyWithPi[A: Ring: Trig](x: A) : A = {
  Trig[A].pi * x
}

Here is how it works: The Ring[T] typeclass is for types that allow multiplication. The Trig[T] typeclass is for types that allow trigonometry. In addition to trigonometric function such as sin, it also comes with values pi and e.

Note that this approach does not rely on converting from the imprecise double approximation to T you can find in Math.PI. E.g. spire has several number types for exact arithmetic. For those, converting from a double approximation would be very imprecise.

The spire.algebra._ import is for all typeclasses, including Trig[T] and Ring[T]. The spire.implicits._ import provides predefined typeclass instances for Float and Double.

To use spire, add the following to your build.sbt:

libraryDependencies += "org.spire-math" %% "spire" % "0.13.0"

Upvotes: 3

stephenjudkins
stephenjudkins

Reputation: 638

This won't work because Math.PI is always a Double and never a Float. Since the Numeric typeclass doesn't supply a value of pi you'll need to add an additional typeclass:

trait Pi[A] { def apply():A }
object Pi {
  implicit val double:Pi[Double] = new Pi[Double] { def apply() = Math.PI }
  implicit val float:Pi[Float] = new Pi[Float] { def apply() = Math.PI.toFloat }    
}

Then you can do the following:

def multiplyWithPi[A](in: A)(implicit num: MyNumeric[A], pi: Pi[A]) : A =
  num.times(pi(), in)

Upvotes: 3

OlivierBlanvillain
OlivierBlanvillain

Reputation: 7768

scala.math.Numeric provides a fromInt method, it should arguably also have fromDouble. Here is how it would look like:

trait MyNumeric[T] {
  def times(a: T, b: T): T
  def fromDouble(d: Double): T
}

object MyNumeric {
  implicit val MyNumericInstance4Double = new MyNumeric[Double] {
    def times(a: Double, b: Double): Double = a * b
    def fromDouble(d: Double): Double = b
  }
  implicit val MyNumericInstance4Float = new MyNumeric[Float] {
    def times(a: Float, b: Float): Float = a * b
    def fromDouble(d: Double): Float = b.toFloat
  }
}

def multiplyWithPi[A](in: A)(implicit num: MyNumeric[A]) : A =
  num.times(num.fromDouble(Math.PI), in)

If you feel that type classes are overkill for your needs, you can also go the unsafe route:

def multiplyWithPi[A](in: A)(implicit num: Numeric[A]): A = 
  (in match {
    case i: Float  => (i * Math.PI).toFloat
    case i: Double => (i * Math.PI).toDouble
  }).asInstanceOf[A]

Or even:

def multiplyWithPi(in: Double): Double = in * Math.PI
def multiplyWithPi(in: Float): Float   = (in * Math.PI).toFloat

Upvotes: 3

Related Questions