-8

How do I compare two numbers. Any numbers. Like Int and Float? I'm not interested in comparing with complex numbers or anything like that. I only want to compare those, which are comparable. Float and Int are.

Suppose you have:

def compareTwoNumbers[???](array:Array[???], number:???) = {
    array(0) > number // this has to compile
}

What do I write instead of ????

Things I've tried so far:

  1. Number.
  2. T <: Number
  3. Numeric (Sorry, I don't understand how to use it in this situation, no examples / documentation is too poor).
Lance Roberts
  • 22,383
  • 32
  • 112
  • 130
  • So you're okay with effects when you comparing `0` and `0.0`? – om-nom-nom Mar 31 '13 at 22:36
  • @om-nom-nom What was the question, again? –  Mar 31 '13 at 22:37
  • actually I've provided bad example (and yeah, I've seen *incomparable with complex numbers and the likes* part). What I'm trying to say is that comparisions on float numbers [may be tricky](http://stackoverflow.com/questions/10334688/how-dangerous-is-it-to-compare-floating-point-values) and by covering it with Number or some general abstraction like `compareTwoNumbers` you're most likely leaving a space where bug can easily hide. – om-nom-nom Mar 31 '13 at 23:11
  • What do you have in your `array:Array[???]`? Some numbers that you know to be either `Int` or `Float` and possibly mixed together? Or you know there are all `Float` or `Int`? – huynhjl Apr 01 '13 at 03:09
  • @huynhjl it could have anything you derived from number, better yet, if it could be anything that has method `>` defined for it, but that sounds like asking too much. –  Apr 01 '13 at 08:42

6 Answers6

4

Just use the ordering typeclass:

def cmp[A](arr: Array[A], number: A)(implicit ord: Ordering[A]) =
  ord.gt(arr(0), number)

// or

def cmp[A](arr: Array[A], number: A)(implicit ord: Ordering[A]) = {
  import ord._
  arr(0) > number
}

scala> cmp(Array(4), 2)
res9: Boolean = true

scala> cmp(Array(BigInt(4)), BigInt(2))
res15: Boolean = true

scala> cmp(Array(4), 2.0)
<console>:9: error: type mismatch;
 found   : Array[Int]
 required: Array[AnyVal]

scala> cmp(Array(4.0), 2)
<console>:9: error: type mismatch;
 found   : Array[Double]
 required: Array[AnyVal]
kiritsuku
  • 52,967
  • 18
  • 114
  • 136
4

I think the main problem is the conversion of numeric types. So let´s encode that:

trait NumericConversion[X, Y] {
  def convert(x: X): Y
}

Of course one have to specify that abstract concept: (for example)

implicit object Int2IntNumericConversion extends NumericConversion[Int, Int] {
  def convert(i: Int): Int = i
}
implicit object Double2DoubleNumericConversion extends NumericConversion[Double, Double] {
  def convert(d: Double): Double = d
}
implicit object Int2DoubleNumericConversion extends NumericConversion[Int, Double] {
  def convert(i: Int): Double = i.toDouble
}

Now the comparing method goes as follows:

def compareTwoNumbers1[N1, N2, N3](n1: N1, n2: N2)
                                  (implicit conv1: NumericConversion[N1, N3], 
                                            conv2: NumericConversion[N2, N3], 
                                              ord: Ordering[N3]): Int = {
  ord compare (conv1 convert n1, conv2 convert n2)
}

Usage:

compareTwoNumbers1[Int, Double, Double](3, 8D)  // -1

What a pitty, we have to explicitly state the type parameters, so I tried:

def compareTwoNumbers2[N3] = new {
  def apply[N1, N2](n1: N1, n2: N2)(implicit conv1: NumericConversion[N1, N3],
                                             conv2: NumericConversion[N2, N3], 
                                               ord: Ordering[N3]): Int = {
    ord compare (conv1 convert n1, conv2 convert n2)
  }
}

That reduces to one type argument:

compareTwoNumbers2[Double](3, 8D)  // -1

Not satisfying, so I tried this:

trait NumericUpperBound[Num1, Num2, UpperBound]
implicit object NumericUpperBoundIDD extends NumericUpperBound[Int, Double, Double]
implicit object NumericUpperBoundDID extends NumericUpperBound[Double, Int, Double]

With a new comparing method:

def compareTwoNumbers3[N1, N2, N3](n1: N1, n2: N2)
                                 (implicit nub: NumericUpperBound[N1, N2, N3], 
                                         conv1: NumericConversion[N1, N3], 
                                         conv2: NumericConversion[N2, N3], 
                                           ord: Ordering[N3]): Int = {
  ord compare (conv1 convert n1, conv2 convert n2)
}

Now it works:

compareTwoNumbers3(3, 8D)  // -1

Of course, type classes for all primitives must be created. But it´s flexible to extend it to BigInt, etc. later on.

EDIT

The comment by @wvxvw which mentions a matrix of NumericUpperBounds inspired me to circumvent a matrix, here is a running example (excluding Byte and Short for the moment):

trait ==>[X, Y] extends (X => Y)

object ==> {
  def apply[X, Y](f: X => Y): X ==> Y = {
    new (X ==> Y) {
      def apply(x: X): Y = f(x)
    }
  }
}

implicit val Int2LongNumericConversion = ==> { x: Int => x.toLong }
implicit val Int2FloatNumericConversion = ==> { x: Int => x.toFloat }
implicit val Int2DoubleNumericConversion = ==> { x: Int => x.toDouble }
implicit val Long2FloatNumericConversion = ==> { x: Long => x.toFloat }
implicit val Long2DoubleNumericConversion = ==> { x: Long => x.toDouble }
implicit val Float2DoubleNumericConversion = ==> { x: Float => x.toDouble }
implicit def reflexiveNumericConversion[X]: X ==> X = new (X ==> X) { def apply(x: X): X = x }

trait NumericUpperBound[Num1, Num2, UpperBound]

implicit def reflexiveNumericUpperBound[X]: NumericUpperBound[X, X, X] = new NumericUpperBound[X, X, X] {}
implicit def inductiveNumericUpperBound1[X, Y](implicit ev: X ==> Y): NumericUpperBound[Y, X, Y] = new NumericUpperBound[Y, X, Y] {}
implicit def inductiveNumericUpperBound2[X, Y](implicit ev: X ==> Y): NumericUpperBound[X, Y, Y] = new NumericUpperBound[X, Y, Y] {}

def compareTwoNumbers[N1, N2, N3](n1: N1, n2: N2)
                                 (implicit nub: NumericUpperBound[N1, N2, N3], 
                                         conv1: N1 ==> N3, 
                                         conv2: N2 ==> N3, 
                                           ord: Ordering[N3]): Int = {
  ord compare (n1, n2)
}

compareTwoNumbers(9L, 13) // -1
Peter Schmitz
  • 5,824
  • 4
  • 26
  • 48
  • The concrete types at method invocation aren´t necessary as I showed. I think someone like Miles Sabin can improve my approach. But you´re right, there´s much stuff around just to compare to numbers. I am looking forward to see that lecture! – Peter Schmitz Apr 01 '13 at 10:44
  • @wvxvw I added an example to prevent a matrix of `NumericUpperBound`s. – Peter Schmitz Apr 01 '13 at 20:07
4

I'm currently learning Scala so don't take my answer too seriously, but I think that View Bounds or Type Constraints could help you. A View Bound allows you to compare any standard numbers since there are implicit views between them but it will not go further because there is no implicit view from BigInt to BigDecimal.

def compareTwoNumbers[A <% Double, B <% Double](array:Array[A], number:B) = array(0) > number

scala> compareTwoNumbers(Array(1, 2, 0), 0.99)
res1: Boolean = true

scala> compareTwoNumbers(Array(1.0, 2, 0), 0.99)
res2: Boolean = true

scala> compareTwoNumbers(Array(1.0, 2, 0), 1)
res3: Boolean = false

I would be interested to see if BigInt and BigDecimal could be supported and to know what are the drawbacks of my solution.

Clément MATHIEU
  • 3,030
  • 23
  • 25
2

Here it is:

def cmp[T1, T2](arr: Array[T1], num: T2)
       (implicit v12: T1 => T2 = null, v21: T2 => T1 = null,
        ord1: Ordering[T1], ord2: Ordering[T2]): Boolean = v12 match {
    case null => ord1.gt(arr(0), num)
    case _    => ord2.gt(arr(0), num)
  }

Some use cases:

scala> cmp(Array(1,2), 0.1)  //T1 = Int, T2 = Double 
res3: Boolean = true

scala> cmp(Array(1.2, 2.3), 1) //T1 = Double, T2 = Int 
res4: Boolean = true

scala> cmp(Array(1,2), BigInt(100))  //T1 = Int, T2 = BigInt 
res5: Boolean = false

scala> cmp(Array(123.5 ,2233.9), BigDecimal(100)) //T1 = Double, T2 = BigDecimal 
res6: Boolean = true

scala> cmp(Array(123.5 ,2233.9), 200.toByte) 
res7: Boolean = true
Eastsun
  • 18,526
  • 6
  • 57
  • 81
1
def compareTwoNumbers(a: Number, b: Number) = {
  a.floatValue() > b.floatValue() // this has to compile
}
kiritsuku
  • 52,967
  • 18
  • 114
  • 136
mmertel
  • 397
  • 2
  • 9
0

It's not that hard adopt this code:

def gt[A : Numeric, B: Numeric](first: A, second: B) = {
    val a = implicitly[Numeric[A]].toDouble(first)
    val b = implicitly[Numeric[B]].toDouble(second)
    a > b
}

Should work with different types of numbers:

scala> gt(4, 4.0)
res3: Boolean = false

scala> gt(4, 3.0)
res4: Boolean = true

scala> gt(3L, 4.0)
res5: Boolean = false
om-nom-nom
  • 62,329
  • 13
  • 183
  • 228
  • @wvxvw 2. then you could either provide a special case for biginteger/bigdecimal (to mitigate performance flaws in general) or look at everything from BigInteger point of view. Both are imperfect solutions, IMO. 1. that is the price of heterogeneous comparisons. I think it is possible to write code that will decrease number of conversions (implicit value classes + double dispatch ftw?), but it definitely would be concise. – om-nom-nom Apr 01 '13 at 10:48