V P V P - 2 months ago 6
Scala Question

Scala: How to check if a double value crossing Double limits?

I want to check if adding some value to a double value exceed the Double limits or not. I tried this:

object Hello {
def main(args: Array[String]): Unit = {
var t = Double.MaxValue
var t2 = t+100000000
if(t2 > 0) {
println("t2 > 0: " + t2)
} else
println("t2 <= 0: " + t2)
}

}


The output I get is

t2 > 0: 1.7976931348623157E308


What I actually want is to sum billions of values and check whether or not the running sum overflows at any time.

Answer

The first part of your question seems to stem from a misunderstanding of floating-point numbers.

  1. IEEE-754 floating-point numbers do not wrap around like some finite-size integers would. Instead, they "saturate" at Double.PositiveInfinity, which represents mathematical infinity. Double.MaxValue is the largest finite positive value of doubles. The next Double after that is Double.PositiveInfinity. Adding any double other than Double.NegativeInfinity and NaNs to Double.PositiveInfinity yields Double.PositiveInfinity.

    scala> Double.PositiveInfinity + 1
    res0: Double = Infinity
    
    scala> Double.PositiveInfinity - 1
    res1: Double = Infinity
    
    scala> Double.PositiveInfinity + Double.NaN
    res2: Double = NaN
    
    scala> Double.PositiveInfinity + Double.NegativeInfinity
    res3: Double = NaN
    
  2. Floating-point numbers get fewer and farther between as their magnitude grows. Double.MaxValue + 100000000 evaluates to Double.MaxValue as a result of roundoff error: Double.MaxValue is so much larger than 100000000 that the former "swallows up" the latter if you try to add them. You would need to add a Double of the order of math.pow(2, -52) * Double.MaxValue to Double.MaxValue in order to get Double.PositiveInfinity:

    scala> math.pow(2,-52) * Double.MaxValue + Double.MaxValue
    res4: Double = Infinity
    

Now, you write

What I actually want is to sum billions of values and check whether or not the running sum overflows at any time.

One possible approach is to define a function that adds the numbers recursively but stops if the running sum is an infinity or a NaN, and wraps the result in an Either[String, Double]:

import scala.collection.immutable

def sumToEither(xs: immutable.Seq[Double]): Either[String, Double] = {
  @annotation.tailrec
  def go(ys: immutable.Seq[Double], acc: Double): Double =
    if (ys.isEmpty || acc.isInfinite || acc.isNaN) acc
    else go(ys.tail, ys.head + acc)
  go(xs, 0.0) match {
    case x if x.isInfinite => Left("overflow")
    case x if x.isNaN => Left("NaN")
    case x => Right(x)
  }
}