From 944b869d04be4c266ed1b3fde87216c14f136561 Mon Sep 17 00:00:00 2001 From: Raphael Jolly Date: Thu, 3 Nov 2022 17:46:42 +0100 Subject: [PATCH] Add StringOps Fixes #554 --- .../collection/compat/PackageShared.scala | 61 ++++ .../collection/compat/StringParsers.scala | 320 ++++++++++++++++++ .../collection/StringParsersJVMTest.scala | 31 ++ .../test/scala/collection/AssertThrown.scala | 49 +++ .../scala/collection/MinMaxOptionTest.scala | 32 +- .../scala/collection/StringParsersTest.scala | 299 ++++++++++++++++ 6 files changed, 761 insertions(+), 31 deletions(-) create mode 100644 compat/src/main/scala-2.11_2.12/scala/collection/compat/StringParsers.scala create mode 100644 compat/src/test/scala-jvm/test/scala/collection/StringParsersJVMTest.scala create mode 100644 compat/src/test/scala/test/scala/collection/AssertThrown.scala create mode 100644 compat/src/test/scala/test/scala/collection/StringParsersTest.scala 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..ed0c8c6b 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,67 @@ 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..74b52720 --- /dev/null +++ b/compat/src/main/scala-2.11_2.12/scala/collection/compat/StringParsers.scala @@ -0,0 +1,320 @@ +/* + * 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..cd69d6c7 --- /dev/null +++ b/compat/src/test/scala-jvm/test/scala/collection/StringParsersJVMTest.scala @@ -0,0 +1,31 @@ +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 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..d338b872 --- /dev/null +++ b/compat/src/test/scala/test/scala/collection/StringParsersTest.scala @@ -0,0 +1,299 @@ +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 doubleSpecificTest(): Unit = doubleExamples.foreach(doubleOK) + + @Test + def doubleGeneralTest(): Unit = forAllExamples.foreach(doubleOK) + + @Test + def floatSpecificTest(): Unit = doubleExamples.foreach(floatOK) + + @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) + +}