diff --git a/README.md b/README.md
index 20cb925e..61f0f236 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,12 @@ All future versions will remain backwards binary compatible with 2.0.0. (The 1.0
 
 ## How it works
 
+The 2.13 and 3.0 versions consist only of an empty `scala.collection.compat` package object, so `import scala.collection.compat._` won't cause an error in cross-compiled code.
+
+The 2.11 and 2.12 versions have the needed compatibility code in this package.
+
+### Changed methods
+
 The 2.13 collections redesign did not break source compatibility for most ordinary code, but there are some exceptions.
 
 For example, the `to` method is used with a type parameter in 2.12:
@@ -37,15 +43,25 @@ import scala.collection.compat._
 xs.to(List)
 ```
 
-The 2.13 and 3.0 versions consist only of an empty `scala.collection.compat` package object, so `import scala.collection.compat._` won't cause an error in cross-compiled code.
-
-The 2.11 and 2.12 versions have the needed compatibility code in this package.
+### New collections
 
 The library also adds backported versions of new collection types, such as `immutable.ArraySeq` and `immutable.LazyList`. (On 2.13, these types are just aliases to standard library types.)
 
-And it adds backported versions of some 2.13 collections methods such as `maxOption`.
+### New collection methods
+
+Support is included for some 2.13 collections methods such as `maxOption`.
+
+### Other new classes
+
+Support is included for some non-collections classes, such as:
+
+* `@nowarn` annotation, added in 2.13.2 and 2.12.13. (The 2.11 `@nowarn` doesn't do anything, but its presence facilitates cross-compilation.)
+
+### Other new methods
+
+Support is included for some other methods, such as:
 
-And, it includes support for some non-collections classes such as the `@nowarn` annotation added in 2.13.2.
+* `toIntOption` (and `Long`, et al) on `String`
 
 ## Migration rules
 
@@ -68,7 +84,7 @@ scalacOptions += "-P:semanticdb:synthetics:on"
 ```bash
 // sbt shell
 > scalafixEnable
-> scalafixAll dependency:Collection213Upgrade@org.scala-lang.modules:scala-collection-migrations:<version> 
+> scalafixAll dependency:Collection213Upgrade@org.scala-lang.modules:scala-collection-migrations:<version>
 ```
 
 ### Collection213CrossCompat
@@ -87,7 +103,7 @@ scalacOptions += "-P:semanticdb:synthetics:on"
 ```bash
 // sbt shell
 > scalafixEnable
-> scalafixAll dependency:Collection213CrossCompat@org.scala-lang.modules:scala-collection-migrations:<version> 
+> scalafixAll dependency:Collection213CrossCompat@org.scala-lang.modules:scala-collection-migrations:<version>
 ```
 
 ### Fixing unused import warnings
diff --git a/compat/src/main/scala-2.11_2.12/scala/collection/compat/PackageShared.scala b/compat/src/main/scala-2.11_2.12/scala/collection/compat/PackageShared.scala
index b905aa02..4bd70b48 100644
--- a/compat/src/main/scala-2.11_2.12/scala/collection/compat/PackageShared.scala
+++ b/compat/src/main/scala-2.11_2.12/scala/collection/compat/PackageShared.scala
@@ -53,6 +53,60 @@ private[compat] trait PackageShared {
     def newBuilder: m.Builder[A, C] = factory()
   }
 
+  implicit class StringOps(s: String) {
+
+    /**
+     * Try to parse as a `Boolean`
+     * @return `Some(true)` if the string is "true" case insensitive,
+     * `Some(false)` if the string is "false" case insensitive,
+     * and `None` if the string is anything else
+     * @throws java.lang.NullPointerException if the string is `null`
+     */
+    def toBooleanOption: Option[Boolean] = StringParsers.parseBool(s)
+
+    /**
+     * Try to parse as a `Byte`
+     * @return `Some(value)` if the string contains a valid byte value, otherwise `None`
+     * @throws java.lang.NullPointerException if the string is `null`
+     */
+    def toByteOption: Option[Byte] = StringParsers.parseByte(s)
+
+    /**
+     * Try to parse as a `Short`
+     * @return `Some(value)` if the string contains a valid short value, otherwise `None`
+     * @throws java.lang.NullPointerException if the string is `null`
+     */
+    def toShortOption: Option[Short] = StringParsers.parseShort(s)
+
+    /**
+     * Try to parse as an `Int`
+     * @return `Some(value)` if the string contains a valid Int value, otherwise `None`
+     * @throws java.lang.NullPointerException if the string is `null`
+     */
+    def toIntOption: Option[Int] = StringParsers.parseInt(s)
+
+    /**
+     * Try to parse as a `Long`
+     * @return `Some(value)` if the string contains a valid long value, otherwise `None`
+     * @throws java.lang.NullPointerException if the string is `null`
+     */
+    def toLongOption: Option[Long] = StringParsers.parseLong(s)
+
+    /**
+     * Try to parse as a `Float`
+     * @return `Some(value)` if the string is a parsable `Float`, `None` otherwise
+     * @throws java.lang.NullPointerException If the string is null
+     */
+    def toFloatOption: Option[Float] = StringParsers.parseFloat(s)
+
+    /**
+     * Try to parse as a `Double`
+     * @return `Some(value)` if the string is a parsable `Double`, `None` otherwise
+     * @throws java.lang.NullPointerException If the string is null
+     */
+    def toDoubleOption: Option[Double] = StringParsers.parseDouble(s)
+  }
+
   implicit def genericCompanionToCBF[A, CC[X] <: GenTraversable[X]](
       fact: GenericCompanion[CC]): CanBuildFrom[Any, A, CC[A]] = {
     /* see https://github.com/scala/scala-collection-compat/issues/337
diff --git a/compat/src/main/scala-2.11_2.12/scala/collection/compat/StringParsers.scala b/compat/src/main/scala-2.11_2.12/scala/collection/compat/StringParsers.scala
new file mode 100644
index 00000000..e23cf1dc
--- /dev/null
+++ b/compat/src/main/scala-2.11_2.12/scala/collection/compat/StringParsers.scala
@@ -0,0 +1,317 @@
+/*
+ * Scala (https://www.scala-lang.org)
+ *
+ * Copyright EPFL and Lightbend, Inc.
+ *
+ * Licensed under Apache License 2.0
+ * (http://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package scala
+package collection
+package compat
+
+import scala.annotation.tailrec
+
+/** A module containing the implementations of parsers from strings to numeric types, and boolean
+ */
+private[scala] object StringParsers {
+
+  //compile-time constant helpers
+
+  //Int.MinValue == -2147483648
+  private final val intOverflowBoundary = -214748364
+  private final val intOverflowDigit    = 9
+  //Long.MinValue == -9223372036854775808L
+  private final val longOverflowBoundary = -922337203685477580L
+  private final val longOverflowDigit    = 9
+
+  @inline
+  private[this] final def decValue(ch: Char): Int = java.lang.Character.digit(ch, 10)
+
+  @inline
+  private[this] final def stepToOverflow(from: String,
+                                         len: Int,
+                                         agg: Int,
+                                         isPositive: Boolean,
+                                         min: Int): Option[Int] = {
+    @tailrec
+    def rec(i: Int, agg: Int): Option[Int] =
+      if (agg < min) None
+      else if (i == len) {
+        if (!isPositive) Some(agg)
+        else if (agg == min) None
+        else Some(-agg)
+      } else {
+        val digit = decValue(from.charAt(i))
+        if (digit == -1) None
+        else rec(i + 1, agg * 10 - digit)
+      }
+    rec(1, agg)
+  }
+
+  @inline
+  private[this] final def isDigit(c: Char): Boolean = c >= '0' && c <= '9'
+
+  //bool
+  @inline
+  final def parseBool(from: String): Option[Boolean] =
+    if (from.equalsIgnoreCase("true")) Some(true)
+    else if (from.equalsIgnoreCase("false")) Some(false)
+    else None
+
+  //integral types
+  final def parseByte(from: String): Option[Byte] = {
+    val len = from.length()
+    //empty strings parse to None
+    if (len == 0) None
+    else {
+      val first = from.charAt(0)
+      val v     = decValue(first)
+      if (len == 1) {
+        //"+" and "-" parse to None
+        if (v > -1) Some(v.toByte)
+        else None
+      } else if (v > -1) stepToOverflow(from, len, -v, true, Byte.MinValue).map(_.toByte)
+      else if (first == '+') stepToOverflow(from, len, 0, true, Byte.MinValue).map(_.toByte)
+      else if (first == '-') stepToOverflow(from, len, 0, false, Byte.MinValue).map(_.toByte)
+      else None
+    }
+  }
+
+  final def parseShort(from: String): Option[Short] = {
+    val len = from.length()
+    //empty strings parse to None
+    if (len == 0) None
+    else {
+      val first = from.charAt(0)
+      val v     = decValue(first)
+      if (len == 1) {
+        //"+" and "-" parse to None
+        if (v > -1) Some(v.toShort)
+        else None
+      } else if (v > -1) stepToOverflow(from, len, -v, true, Short.MinValue).map(_.toShort)
+      else if (first == '+') stepToOverflow(from, len, 0, true, Short.MinValue).map(_.toShort)
+      else if (first == '-') stepToOverflow(from, len, 0, false, Short.MinValue).map(_.toShort)
+      else None
+    }
+  }
+
+  final def parseInt(from: String): Option[Int] = {
+    val len = from.length()
+
+    @tailrec
+    def step(i: Int, agg: Int, isPositive: Boolean): Option[Int] = {
+      if (i == len) {
+        if (!isPositive) Some(agg)
+        else if (agg == Int.MinValue) None
+        else Some(-agg)
+      } else if (agg < intOverflowBoundary) None
+      else {
+        val digit = decValue(from.charAt(i))
+        if (digit == -1 || (agg == intOverflowBoundary && digit == intOverflowDigit)) None
+        else step(i + 1, (agg * 10) - digit, isPositive)
+      }
+    }
+    //empty strings parse to None
+    if (len == 0) None
+    else {
+      val first = from.charAt(0)
+      val v     = decValue(first)
+      if (len == 1) {
+        //"+" and "-" parse to None
+        if (v > -1) Some(v)
+        else None
+      } else if (v > -1) step(1, -v, true)
+      else if (first == '+') step(1, 0, true)
+      else if (first == '-') step(1, 0, false)
+      else None
+    }
+  }
+
+  final def parseLong(from: String): Option[Long] = {
+    //like parseInt, but Longer
+    val len = from.length()
+
+    @tailrec
+    def step(i: Int, agg: Long, isPositive: Boolean): Option[Long] = {
+      if (i == len) {
+        if (isPositive && agg == Long.MinValue) None
+        else if (isPositive) Some(-agg)
+        else Some(agg)
+      } else if (agg < longOverflowBoundary) None
+      else {
+        val digit = decValue(from.charAt(i))
+        if (digit == -1 || (agg == longOverflowBoundary && digit == longOverflowDigit)) None
+        else step(i + 1, agg * 10 - digit, isPositive)
+      }
+    }
+    //empty strings parse to None
+    if (len == 0) None
+    else {
+      val first = from.charAt(0)
+      val v     = decValue(first).toLong
+      if (len == 1) {
+        //"+" and "-" parse to None
+        if (v > -1) Some(v)
+        else None
+      } else if (v > -1) step(1, -v, true)
+      else if (first == '+') step(1, 0, true)
+      else if (first == '-') step(1, 0, false)
+      else None
+    }
+  }
+
+  //floating point
+  final def checkFloatFormat(format: String): Boolean = {
+    //indices are tracked with a start index which points *at* the first index
+    //and an end index which points *after* the last index
+    //so that slice length === end - start
+    //thus start == end <=> empty slice
+    //and format.substring(start, end) is equivalent to the slice
+
+    //some utilities for working with index bounds into the original string
+    @inline
+    def forAllBetween(start: Int, end: Int, pred: Char => Boolean): Boolean = {
+      @tailrec
+      def rec(i: Int): Boolean = i >= end || pred(format.charAt(i)) && rec(i + 1)
+      rec(start)
+    }
+
+    //one after last index for the predicate to hold, or `from` if none hold
+    //may point after the end of the string
+    @inline
+    def skipIndexWhile(predicate: Char => Boolean, from: Int, until: Int): Int = {
+      @tailrec @inline
+      def rec(i: Int): Int =
+        if ((i < until) && predicate(format.charAt(i))) rec(i + 1)
+        else i
+      rec(from)
+    }
+
+    def isHexFloatLiteral(startIndex: Int, endIndex: Int): Boolean = {
+      def isHexDigit(ch: Char) =
+        ((ch >= '0' && ch <= '9') ||
+          (ch >= 'a' && ch <= 'f') ||
+          (ch >= 'A' && ch <= 'F'))
+
+      def prefixOK(startIndex: Int, endIndex: Int): Boolean = {
+        val len = endIndex - startIndex
+        (len > 0) && {
+          //the prefix part is
+          //hexDigits
+          //hexDigits.
+          //hexDigits.hexDigits
+          //.hexDigits
+          //but not .
+          if (format.charAt(startIndex) == '.') {
+            (len > 1) && forAllBetween(startIndex + 1, endIndex, isHexDigit)
+          } else {
+            val noLeading = skipIndexWhile(isHexDigit, startIndex, endIndex)
+            (noLeading >= endIndex) ||
+            ((format.charAt(noLeading) == '.') && forAllBetween(noLeading + 1,
+                                                                endIndex,
+                                                                isHexDigit))
+          }
+        }
+      }
+
+      def postfixOK(startIndex: Int, endIndex: Int): Boolean =
+        (startIndex < endIndex) && {
+          (forAllBetween(startIndex, endIndex, isDigit)) || {
+            val startchar = format.charAt(startIndex)
+            (startchar == '+' || startchar == '-') &&
+            (endIndex - startIndex > 1) &&
+            forAllBetween(startIndex + 1, endIndex, isDigit)
+          }
+        }
+      // prefix [pP] postfix
+      val pIndex = format.indexWhere(ch => ch == 'p' || ch == 'P', startIndex)
+      (pIndex <= endIndex) && prefixOK(startIndex, pIndex) && postfixOK(pIndex + 1, endIndex)
+    }
+
+    def isDecFloatLiteral(startIndex: Int, endIndex: Int): Boolean = {
+      //invariant: endIndex > startIndex
+
+      def isExp(c: Char): Boolean = c == 'e' || c == 'E'
+
+      def expOK(startIndex: Int, endIndex: Int): Boolean =
+        (startIndex < endIndex) && {
+          val startChar = format.charAt(startIndex)
+          if (startChar == '+' || startChar == '-')
+            (endIndex > (startIndex + 1)) &&
+            skipIndexWhile(isDigit, startIndex + 1, endIndex) == endIndex
+          else skipIndexWhile(isDigit, startIndex, endIndex) == endIndex
+        }
+
+      //significant can be one of
+      //* digits.digits
+      //* .digits
+      //* digits.
+      //but not just .
+      val startChar = format.charAt(startIndex)
+      if (startChar == '.') {
+        val noSignificant = skipIndexWhile(isDigit, startIndex + 1, endIndex)
+        // a digit is required followed by optional exp
+        (noSignificant > startIndex + 1) && (noSignificant >= endIndex ||
+        isExp(format.charAt(noSignificant)) && expOK(noSignificant + 1, endIndex))
+      } else if (isDigit(startChar)) {
+        // one set of digits, then optionally a period, then optionally another set of digits, then optionally an exponent
+        val noInt = skipIndexWhile(isDigit, startIndex, endIndex)
+        // just the digits
+        (noInt == endIndex) || {
+          if (format.charAt(noInt) == '.') {
+            val noSignificant = skipIndexWhile(isDigit, noInt + 1, endIndex)
+            (noSignificant >= endIndex) || //no exponent
+            isExp(format.charAt(noSignificant)) && expOK(noSignificant + 1, endIndex)
+          } else
+            isExp(format.charAt(noInt)) && expOK(noInt + 1, endIndex)
+        }
+      } else false
+    }
+
+    //count 0x00 to 0x20 as "whitespace", and nothing else
+    val unspacedStart = format.indexWhere(ch => ch.toInt > 0x20)
+    val unspacedEnd   = format.lastIndexWhere(ch => ch.toInt > 0x20) + 1
+
+    if (unspacedStart == -1 || unspacedStart >= unspacedEnd || unspacedEnd <= 0) false
+    else {
+      //all formats can have a sign
+      val unsigned = {
+        val startchar = format.charAt(unspacedStart)
+        if (startchar == '-' || startchar == '+') unspacedStart + 1 else unspacedStart
+      }
+      if (unsigned >= unspacedEnd) false
+      //that's it for NaN and Infinity
+      else if (format.charAt(unsigned) == 'N') format.substring(unsigned, unspacedEnd) == "NaN"
+      else if (format.charAt(unsigned) == 'I') format.substring(unsigned, unspacedEnd) == "Infinity"
+      else {
+        //all other formats can have a format suffix
+        val desuffixed = {
+          val endchar = format.charAt(unspacedEnd - 1)
+          if (endchar == 'f' || endchar == 'F' || endchar == 'd' || endchar == 'D') unspacedEnd - 1
+          else unspacedEnd
+        }
+        val len = desuffixed - unsigned
+        if (len <= 0) false
+        else if (len >= 2 && (format.charAt(unsigned + 1) == 'x' || format.charAt(unsigned + 1) == 'X'))
+          format.charAt(unsigned) == '0' && isHexFloatLiteral(unsigned + 2, desuffixed)
+        else isDecFloatLiteral(unsigned, desuffixed)
+      }
+    }
+  }
+
+  @inline
+  def parseFloat(from: String): Option[Float] =
+    if (checkFloatFormat(from)) Some(java.lang.Float.parseFloat(from))
+    else None
+
+  @inline
+  def parseDouble(from: String): Option[Double] =
+    if (checkFloatFormat(from)) Some(java.lang.Double.parseDouble(from))
+    else None
+
+}
diff --git a/compat/src/test/scala-jvm/test/scala/collection/StringParsersJVMTest.scala b/compat/src/test/scala-jvm/test/scala/collection/StringParsersJVMTest.scala
new file mode 100644
index 00000000..afe5d714
--- /dev/null
+++ b/compat/src/test/scala-jvm/test/scala/collection/StringParsersJVMTest.scala
@@ -0,0 +1,37 @@
+package test.scala.collection
+
+import org.junit.Test
+import org.junit.Assert._
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+import scala.collection.compat._
+
+@RunWith(classOf[JUnit4])
+class StringParsersJVMTest extends StringParsersTest {
+
+  @Test
+  def doubleSpecificTest(): Unit = doubleExamples.foreach(doubleOK)
+
+  @Test
+  def floatSpecificTest(): Unit = doubleExamples.foreach(floatOK)
+
+  @Test
+  def nullByte(): Unit = assertThrows[NullPointerException](nullstring.toByteOption)
+
+  @Test
+  def nullShort(): Unit = assertThrows[NullPointerException](nullstring.toShortOption)
+
+  @Test
+  def nullInt(): Unit = assertThrows[NullPointerException](nullstring.toIntOption)
+
+  @Test
+  def nullLong(): Unit = assertThrows[NullPointerException](nullstring.toLongOption)
+
+  @Test
+  def nullFloat(): Unit = assertThrows[NullPointerException](nullstring.toFloatOption)
+
+  @Test
+  def nullDouble(): Unit = assertThrows[NullPointerException](nullstring.toDoubleOption)
+
+}
diff --git a/compat/src/test/scala/test/scala/collection/AssertThrown.scala b/compat/src/test/scala/test/scala/collection/AssertThrown.scala
new file mode 100644
index 00000000..04a66fd2
--- /dev/null
+++ b/compat/src/test/scala/test/scala/collection/AssertThrown.scala
@@ -0,0 +1,49 @@
+/*
+ * Scala (https://www.scala-lang.org)
+ *
+ * Copyright EPFL and Lightbend, Inc.
+ *
+ * Licensed under Apache License 2.0
+ * (http://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package test.scala.collection
+
+import org.junit.Assert._
+
+import scala.reflect.ClassTag
+import scala.util.control.NonFatal
+
+class AssertThrown {
+
+  // next two methods copied from AssertUtil in scala/scala repo
+
+  /** Check that throwable T (or a subclass) was thrown during evaluation of `body`,
+   *  and that its message satisfies the `checkMessage` predicate.
+   *  Any other exception is propagated.
+   */
+  def assertThrows[T <: Throwable: ClassTag](body: => Any,
+                                             checkMessage: String => Boolean = s => true): Unit = {
+    assertThrown[T](t => checkMessage(t.getMessage))(body)
+  }
+
+  def assertThrown[T <: Throwable: ClassTag](checker: T => Boolean)(body: => Any): Unit =
+    try {
+      body
+      fail("Expression did not throw!")
+    } catch {
+      case e: T if checker(e) => ()
+      case failed: T =>
+        val ae = new AssertionError(s"Exception failed check: $failed")
+        ae.addSuppressed(failed)
+        throw ae
+      case NonFatal(other) =>
+        val ae = new AssertionError(
+          s"Wrong exception: expected ${implicitly[ClassTag[T]]} but was ${other.getClass.getName}")
+        ae.addSuppressed(other)
+        throw ae
+    }
+}
diff --git a/compat/src/test/scala/test/scala/collection/MinMaxOptionTest.scala b/compat/src/test/scala/test/scala/collection/MinMaxOptionTest.scala
index c352d7e9..0effc456 100644
--- a/compat/src/test/scala/test/scala/collection/MinMaxOptionTest.scala
+++ b/compat/src/test/scala/test/scala/collection/MinMaxOptionTest.scala
@@ -16,45 +16,15 @@ import org.junit.Assert._
 import org.junit.Test
 
 import scala.util.Random
-import scala.reflect.ClassTag
-import scala.util.control.NonFatal
 
 import scala.collection.compat._
 
 // whole file copied/adapted from same-named file in scala/scala repo
 
 /* Test for scala/bug#7614 */
-class MinByMaxByTest {
+class MinByMaxByTest extends AssertThrown {
   val list = List.fill(1000)(Random.nextInt(10000) - 5000)
 
-  // next two methods copied from AssertUtil in scala/scala repo
-
-  /** Check that throwable T (or a subclass) was thrown during evaluation of `body`,
-   *  and that its message satisfies the `checkMessage` predicate.
-   *  Any other exception is propagated.
-   */
-  def assertThrows[T <: Throwable: ClassTag](body: => Any,
-                                             checkMessage: String => Boolean = s => true): Unit = {
-    assertThrown[T](t => checkMessage(t.getMessage))(body)
-  }
-
-  def assertThrown[T <: Throwable: ClassTag](checker: T => Boolean)(body: => Any): Unit =
-    try {
-      body
-      fail("Expression did not throw!")
-    } catch {
-      case e: T if checker(e) => ()
-      case failed: T =>
-        val ae = new AssertionError(s"Exception failed check: $failed")
-        ae.addSuppressed(failed)
-        throw ae
-      case NonFatal(other) =>
-        val ae = new AssertionError(
-          s"Wrong exception: expected ${implicitly[ClassTag[T]]} but was ${other.getClass.getName}")
-        ae.addSuppressed(other)
-        throw ae
-    }
-
   // Basic emptiness check
   @Test
   def checkEmpty(): Unit = {
diff --git a/compat/src/test/scala/test/scala/collection/StringParsersTest.scala b/compat/src/test/scala/test/scala/collection/StringParsersTest.scala
new file mode 100644
index 00000000..1b6e79d4
--- /dev/null
+++ b/compat/src/test/scala/test/scala/collection/StringParsersTest.scala
@@ -0,0 +1,301 @@
+package test.scala.collection
+
+import org.junit.Test
+import org.junit.Assert._
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import scala.util.Try
+
+import scala.collection.compat._
+
+@RunWith(classOf[JUnit4])
+class StringParsersTest extends AssertThrown {
+
+  def doubleOK(str: String): Unit =
+    assertTrue(
+      s"str.toDouble <> str.toDoubleOption for $str",
+      (str.toDoubleOption, Try(str.toDouble).toOption) match {
+        case (Some(d1), Some(d2)) => d1.isNaN && d2.isNaN || d1 == d2
+        case (o1, o2)             => o1 == o2
+      }
+    )
+
+  def floatOK(str: String): Unit =
+    assertTrue(
+      s"str.toFloat <> str.toFloatOption for $str",
+      (str.toFloatOption, Try(str.toFloat).toOption) match {
+        case (Some(f1), Some(f2)) if f1.isNaN && f2.isNaN => true
+        case (o1, o2)                                     => o1 == o2
+      }
+    )
+
+  def byteOK(str: String): Unit =
+    assertTrue(s"str.toByte <> str.toByteOption for $str",
+               str.toByteOption == Try(str.toByte).toOption)
+
+  def shortOK(str: String): Unit =
+    assertTrue(s"str.toShort <> str.toShortOption for $str",
+               str.toShortOption == Try(str.toShort).toOption)
+
+  def intOK(str: String): Unit =
+    assertTrue(s"str.toInt <> str.toIntOption for $str", str.toIntOption == Try(str.toInt).toOption)
+
+  def longOK(str: String): Unit =
+    assertTrue(s"str.toLong <> str.toLongOption for $str",
+               str.toLongOption == Try(str.toLong).toOption)
+
+  val forAllExamples = List("", "+", "-", "0", "-0", "+0", "1", "-1", "+1")
+
+  val nearOverflow = for {
+    b <- List[Int](Byte.MinValue,
+                   Byte.MaxValue,
+                   Short.MinValue,
+                   Short.MaxValue,
+                   Int.MinValue,
+                   Int.MaxValue)
+    l = b.toLong
+    d  <- (-10 to 10)
+    ii <- List(l + d)
+  } yield ii.toString
+
+  val noLongOverflow = List(Long.MinValue, Long.MinValue + 1, Long.MaxValue, Long.MaxValue - 1)
+
+  val longOverUnderflow = List("9223372036854775808", "-9223372036854775809")
+
+  val longNearOverflow = noLongOverflow.map(_.toString) ::: longOverUnderflow
+
+  val nullstring: String = null
+
+  //test cases taken from Apache Harmony: https://android.googlesource.com/platform/libcore/+/master/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/DoubleTest.java
+  val doubleExamples = List(
+    "-1.233999999999999965116738099630936817275852021384209929081813042837802886790127428328465579708849276001782791006814286802871737087810957327493372866733334925806221045495205250590286471187577636646208155890426896101636282423463443661040209738873506655844025580428394216030152374941053494694642722606658935546875E-112",
+    "2.4703282292062327208828439643411e-324",
+    "2.4703282292062327208828439643412e-324",
+    "3.4e-0",
+    "3.4e-1",
+    "3.4e-323",
+    "3.4e-324",
+    "1.2e0",
+    "1.2e308",
+    "1.2e309",
+    "1.2e310",
+    "3.4e-324",
+    "0.0p0D",
+    "+0x.p1d",
+    "0Xg.gp1D",
+    "-0x1.1p",
+    "+0x 1.1 p2d",
+    "x1.1p2d",
+    " 0x-2.1p2",
+    " 0x2.1pad",
+    " 0x111.222p 22d",
+    "0x0.0p0D",
+    "0xa.ap+9d",
+    "+0Xb.10ap8",
+    "-0X.a0P2D",
+    "\r 0x22.1p2d \t",
+    "0x1.0p-1",
+    "0x00000000000000000000000000000000001.0p-1",
+    "0x1.0p-00000000000000000000000000001",
+    "0x.100000000000000000000000000000000p1",
+    "0x0.0p999999999999999999999999999999999999999999999999999999999999999",
+    "0xf1.0p9999999999999999999999999999999999999999999999999999999999999999",
+    "0xffffffffffffffffffffffffffffffffffff.ffffffffffffffffffffffffffffffffffffffffffffffp1",
+    "0x0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001p1600",
+    "0x0.0p-999999999999999999999999999999999999999999999999999999",
+    "0xf1.0p-9999999999999999999999999999999999999999999999999999999999999999",
+    "0x10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000p-1600",
+    "0x1.p9223372036854775807",
+    "0x1.p9223372036854775808",
+    "0x10.p9223372036854775808",
+    "0xabcd.ffffffffp+2000",
+    "0x1.p-9223372036854775808",
+    "0x1.p-9223372036854775809",
+    "0x.1p-9223372036854775809",
+    "0xabcd.ffffffffffffffp-2000",
+    "0x1.fffffffffffffp1023",
+    "0x1.fffffffffffff000000000000000000000000001p1023",
+    "0x1.fffffffffffff1p1023",
+    "0x1.fffffffffffff100000000000000000000000001p1023",
+    "0x1.fffffffffffff1fffffffffffffffffffffffffffffffffffffffffffffp1023",
+    "0x1.fffffffffffff7p1023",
+    "0x1.fffffffffffff700000000000000000000000001p1023",
+    "0x1.fffffffffffff8p1023",
+    "0x1.fffffffffffff800000000000000000000000001p1023",
+    "0x1.fffffffffffff8fffffffffffffffffffffffffffffffffffffffffffffp1023",
+    "0x1.fffffffffffff9p1023",
+    "0x1.fffffffffffff900000000000000000000000001p1023",
+    "0x1.ffffffffffffffp1023",
+    "0x1.ffffffffffffff00000000000000000000000001p1023",
+    "0x1.fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffp1023",
+    "-0x1.fffffffffffffp1023",
+    "-0x1.fffffffffffff000000000000000000000000001p1023",
+    "-0x1.fffffffffffff1p1023",
+    "-0x1.fffffffffffff100000000000000000000000001p1023",
+    "-0x1.fffffffffffff1fffffffffffffffffffffffffffffffffffffffffffffp1023",
+    "-0x1.fffffffffffff7p1023",
+    "-0x1.fffffffffffff700000000000000000000000001p1023",
+    "-0x1.fffffffffffff8p1023",
+    "-0x1.fffffffffffff800000000000000000000000001p1023",
+    "-0x1.fffffffffffff8fffffffffffffffffffffffffffffffffffffffffffffp1023",
+    "-0x1.fffffffffffff9p1023",
+    "-0x1.fffffffffffff900000000000000000000000001p1023",
+    "-0x1.ffffffffffffffp1023",
+    "-0x1.ffffffffffffff00000000000000000000000001p1023",
+    "-0x1.fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffp1023",
+    "0x1.0p-1022",
+    "0x1.00000000000001p-1022",
+    "0x1.000000000000010000000000000000001p-1022",
+    "0x1.00000000000001fffffffffffffffffffffffffffffffffp-1022",
+    "0x1.00000000000007p-1022",
+    "0x1.000000000000070000000000000000001p-1022",
+    "0x1.00000000000007fffffffffffffffffffffffffffffffffp-1022",
+    "0x1.00000000000008p-1022",
+    "0x1.000000000000080000000000000000001p-1022",
+    "0x1.00000000000008fffffffffffffffffffffffffffffffffp-1022",
+    "0x1.00000000000009p-1022",
+    "0x1.000000000000090000000000000000001p-1022",
+    "0x1.00000000000009fffffffffffffffffffffffffffffffffp-1022",
+    "0x1.0000000000000fp-1022",
+    "0x1.0000000000000ffffffffffffffffffffffffffffffffffp-1022",
+    "-0x1.0p-1022",
+    "-0x1.00000000000001p-1022",
+    "-0x1.000000000000010000000000000000001p-1022",
+    "-0x1.00000000000001fffffffffffffffffffffffffffffffffp-1022",
+    "-0x1.00000000000007p-1022",
+    "-0x1.000000000000070000000000000000001p-1022",
+    "-0x1.00000000000007fffffffffffffffffffffffffffffffffp-1022",
+    "-0x1.00000000000008p-1022",
+    "-0x1.000000000000080000000000000000001p-1022",
+    "-0x1.00000000000008fffffffffffffffffffffffffffffffffp-1022",
+    "-0x1.00000000000009p-1022",
+    "-0x1.000000000000090000000000000000001p-1022",
+    "-0x1.00000000000009fffffffffffffffffffffffffffffffffp-1022",
+    "-0x1.0000000000000fp-1022",
+    "-0x1.0000000000000ffffffffffffffffffffffffffffffffffp-1022",
+    "0x0.fffffffffffffp-1022",
+    "0x0.fffffffffffff00000000000000000000000000000000001p-1022",
+    "0x0.fffffffffffff1p-1022",
+    "0x0.fffffffffffff10000000000000000000000000000000001p-1022",
+    "0x0.fffffffffffff1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffp-1022",
+    "0x0.fffffffffffff7p-1022",
+    "0x0.fffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffp-1022",
+    "0x0.fffffffffffff8p-1022",
+    "0x0.fffffffffffff80000000000000000000000000000000001p-1022",
+    "0x0.fffffffffffff8ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffp-1022",
+    "0x0.fffffffffffff9p-1022",
+    "0x0.fffffffffffff9ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffp-1022",
+    "0x0.ffffffffffffffp-1022",
+    "0x0.ffffffffffffff0000000000000000000000000000000001p-1022",
+    "0x0.ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffp-1022",
+    "-0x0.fffffffffffffp-1022",
+    "-0x0.fffffffffffff00000000000000000000000000000000001p-1022",
+    "-0x0.fffffffffffff1p-1022",
+    "-0x0.fffffffffffff10000000000000000000000000000000001p-1022",
+    "-0x0.fffffffffffff1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffp-1022",
+    "-0x0.fffffffffffff7p-1022",
+    "-0x0.fffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffp-1022",
+    "-0x0.fffffffffffff8p-1022",
+    "-0x0.fffffffffffff80000000000000000000000000000000001p-1022",
+    "-0x0.fffffffffffff8ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffp-1022",
+    "-0x0.fffffffffffff9p-1022",
+    "-0x0.fffffffffffff9ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffp-1022",
+    "-0x0.ffffffffffffffp-1022",
+    "-0x0.ffffffffffffff0000000000000000000000000000000001p-1022",
+    "-0x0.ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffp-1022",
+    "0x0.0000000000001p-1022",
+    "0x0.00000000000010000000000000000001p-1022",
+    "0x0.0000000000001fffffffffffffffffffffffffffffffffp-1022",
+    "0x0.00000000000017p-1022",
+    "0x0.000000000000170000000000000000001p-1022",
+    "0x0.00000000000017fffffffffffffffffffffffffffffffffp-1022",
+    "0x0.00000000000018p-1022",
+    "0x0.000000000000180000000000000000001p-1022",
+    "0x0.00000000000018fffffffffffffffffffffffffffffffffp-1022",
+    "0x0.00000000000019p-1022",
+    "0x0.000000000000190000000000000000001p-1022",
+    "0x0.00000000000019fffffffffffffffffffffffffffffffffp-1022",
+    "0x0.0000000000001fp-1022",
+    "0x0.0000000000001f0000000000000000001p-1022",
+    "0x0.0000000000001ffffffffffffffffffffffffffffffffffp-1022",
+    "-0x0.0000000000001p-1022",
+    "-0x0.00000000000010000000000000000001p-1022",
+    "-0x0.0000000000001fffffffffffffffffffffffffffffffffp-1022",
+    "-0x0.00000000000017p-1022",
+    "-0x0.000000000000170000000000000000001p-1022",
+    "-0x0.00000000000017fffffffffffffffffffffffffffffffffp-1022",
+    "-0x0.00000000000018p-1022",
+    "-0x0.000000000000180000000000000000001p-1022",
+    "-0x0.00000000000018fffffffffffffffffffffffffffffffffp-1022",
+    "-0x0.00000000000019p-1022",
+    "-0x0.000000000000190000000000000000001p-1022",
+    "-0x0.00000000000019fffffffffffffffffffffffffffffffffp-1022",
+    "-0x0.0000000000001fp-1022",
+    "-0x0.0000000000001f0000000000000000001p-1022",
+    "-0x0.0000000000001ffffffffffffffffffffffffffffffffffp-1022",
+    "0x0.00000000000004p-1022",
+    "0x0.00000000000007ffffffffffffffffffffffp-1022",
+    "0x0.00000000000008p-1022",
+    "0x0.000000000000080000000000000000001p-1022",
+    "0x0.00000000000008fffffffffffffffffffffffffffffffp-1022",
+    "0x0.00000000000009p-1022",
+    "0x0.000000000000090000000000000000001p-1022",
+    "0x0.00000000000009fffffffffffffffffffffffffffffffffp-1022",
+    "0x0.0000000000000fffffffffffffffffffffffffffffffffffp-1022",
+    "-0x0.00000000000004p-1022",
+    "-0x0.00000000000007ffffffffffffffffffffffp-1022",
+    "-0x0.00000000000008p-1022",
+    "-0x0.000000000000080000000000000000001p-1022",
+    "-0x0.00000000000008fffffffffffffffffffffffffffffffp-1022",
+    "-0x0.00000000000009p-1022",
+    "-0x0.000000000000090000000000000000001p-1022",
+    "-0x0.00000000000009fffffffffffffffffffffffffffffffffp-1022",
+    "-0x0.0000000000000fffffffffffffffffffffffffffffffffffp-1022",
+    "",
+    ".",
+    ".4",
+    ".E4",
+    ".E",
+    ".x",
+    ".1E4",
+    "4.",
+    "1.1E4",
+    "1.E4",
+    "1E4",
+    "E4",
+    "0.0",
+    "+0.0",
+    "-0.0",
+    "NaN",
+    "+NaN",
+    "-NaN",
+    "Infinity",
+    "+Infinity",
+    "-Infinity",
+    "NaNd",
+    "+NaNd",
+    "-NaNd",
+    "Infinityd",
+    "+Infinityd",
+    "-Infinityd"
+  )
+
+  @Test
+  def doubleGeneralTest(): Unit = forAllExamples.foreach(doubleOK)
+
+  @Test
+  def floatGeneralTest(): Unit = forAllExamples.foreach(floatOK)
+
+  @Test
+  def byteTest(): Unit = (forAllExamples ::: nearOverflow).foreach(byteOK)
+
+  @Test
+  def shortTest(): Unit = (forAllExamples ::: nearOverflow).foreach(shortOK)
+
+  @Test
+  def intTest(): Unit = (forAllExamples ::: nearOverflow).foreach(intOK)
+
+  @Test
+  def longTest(): Unit = (forAllExamples ::: longNearOverflow).foreach(longOK)
+
+}