From 7db4283013ba9bbcb7446a838364d7730c17194a Mon Sep 17 00:00:00 2001 From: Matthew de Detrich Date: Tue, 11 Jul 2017 22:33:49 +0200 Subject: [PATCH 1/5] Added in various JNumber conversions #37 There is now a constructedFlag which tracks how a JNumber is created --- ...rk.scala => EqualsHashcodeBenchmark.scala} | 5 +- .../ast/JNumberConversionBenchmark.scala | 79 +++++++++++++ .../scala-2.10/scalajson.ast/JValue.scala | 105 ++++++++++++++--- .../scalajson.ast/unsafe/JValue.scala | 102 +++++++++++++++-- js/src/main/scala/scalajson/ast/JValue.scala | 102 +++++++++++++++-- .../scala/scalajson/ast/unsafe/JValue.scala | 106 +++++++++++++++--- .../scala-2.10/scalajson.ast/JValue.scala | 100 +++++++++++++++-- .../scalajson.ast/unsafe/JValue.scala | 100 +++++++++++++++-- jvm/src/main/scala/scalajson/ast/JValue.scala | 100 +++++++++++++++-- .../scala/scalajson/ast/unsafe/JValue.scala | 103 +++++++++++++++-- .../scala-2.10/scalajson.ast/package.scala | 21 ++++ .../main/scala/scalajson/ast/package.scala | 21 ++++ .../test/scala/specs/unsafe/Generators.scala | 2 +- 13 files changed, 841 insertions(+), 105 deletions(-) rename benchmark/jvm/src/test/scala/scalajson/ast/{PrivateBenchmark.scala => EqualsHashcodeBenchmark.scala} (78%) create mode 100644 benchmark/jvm/src/test/scala/scalajson/ast/JNumberConversionBenchmark.scala diff --git a/benchmark/jvm/src/test/scala/scalajson/ast/PrivateBenchmark.scala b/benchmark/jvm/src/test/scala/scalajson/ast/EqualsHashcodeBenchmark.scala similarity index 78% rename from benchmark/jvm/src/test/scala/scalajson/ast/PrivateBenchmark.scala rename to benchmark/jvm/src/test/scala/scalajson/ast/EqualsHashcodeBenchmark.scala index 939ac6d..4401d23 100644 --- a/benchmark/jvm/src/test/scala/scalajson/ast/PrivateBenchmark.scala +++ b/benchmark/jvm/src/test/scala/scalajson/ast/EqualsHashcodeBenchmark.scala @@ -3,10 +3,7 @@ package scalajson.ast import benchmark.Generators import org.scalameter.Bench -/** - * Created by matthewdedetrich on 17/10/16. - */ -object PrivateBenchmark extends Bench.ForkedTime { +object EqualsHashcodeBenchmark extends Bench.ForkedTime { performance of "privateMethods" in { measure method "hashcode" in { diff --git a/benchmark/jvm/src/test/scala/scalajson/ast/JNumberConversionBenchmark.scala b/benchmark/jvm/src/test/scala/scalajson/ast/JNumberConversionBenchmark.scala new file mode 100644 index 0000000..9cae717 --- /dev/null +++ b/benchmark/jvm/src/test/scala/scalajson/ast/JNumberConversionBenchmark.scala @@ -0,0 +1,79 @@ +package scalajson.ast + +import org.scalameter.{Bench, Gen} + +object JNumberConversionBenchmark extends Bench.ForkedTime { + def intString: Gen[String] = + for { + size <- Gen.range("seed")(300000, 1500000, 300000) + } yield { + size.toString + } + + def floatString: Gen[String] = + for { + a <- Gen.range("seed")(300000, 1500000, 300000) + b <- Gen.range("seed")(300000, 1500000, 300000) + } yield { + s"$a.$b".toFloat.toString + } + + private val intConstructedFlag = NumberFlags.intConstructed + private val floatConstructedFlag = NumberFlags.floatConstructed + + performance of "intBitFlagCheckSuccess" in { + using(intString) in { value: String => + if ((intConstructedFlag & NumberFlags.int) == NumberFlags.int) + Some(value.toInt) + else { + try { + val asInt = value.toInt + if (BigInt(value) == BigInt(asInt)) + Some(asInt) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + } + + performance of "intManualCheckSuccess" in { + using(intString) in { value: String => + try { + val asInt = value.toInt + if (BigInt(value) == BigInt(asInt)) + Some(asInt) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + + performance of "floatBitFlagCheckSuccess" in { + using(floatString) in { value: String => + if ((floatConstructedFlag & NumberFlags.float) == NumberFlags.float) + Some(value.toFloat) + else { + val asFloat = value.toFloat + if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) + Some(asFloat) + else + None + } + } + } + + performance of "floatManualCheckSuccess" in { + using(floatString) in { value: String => + val asFloat = value.toFloat + if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) + Some(asFloat) + else + None + } + } +} diff --git a/js/src/main/scala-2.10/scalajson.ast/JValue.scala b/js/src/main/scala-2.10/scalajson.ast/JValue.scala index df9cacc..844d296 100644 --- a/js/src/main/scala-2.10/scalajson.ast/JValue.scala +++ b/js/src/main/scala-2.10/scalajson.ast/JValue.scala @@ -50,15 +50,20 @@ final case class JString(value: String) extends JValue { } object JNumber { - def apply(value: Int): JNumber = new JNumber(value.toString) + def apply(value: Int): JNumber = + new JNumber(value.toString)(NumberFlags.intConstructed) - def apply(value: Integer): JNumber = new JNumber(value.toString) + def apply(value: Integer): JNumber = + new JNumber(value.toString)(NumberFlags.intConstructed) - def apply(value: Long): JNumber = new JNumber(value.toString) + def apply(value: Long): JNumber = + new JNumber(value.toString)(NumberFlags.longConstructed) - def apply(value: BigInt): JNumber = new JNumber(value.toString()) + def apply(value: BigInt): JNumber = + new JNumber(value.toString())(NumberFlags.bigIntConstructed) - def apply(value: BigDecimal): JNumber = new JNumber(value.toString()) + def apply(value: BigDecimal): JNumber = + new JNumber(value.toString())(NumberFlags.bigDecimalConstructed) /** * @param value @@ -67,7 +72,7 @@ object JNumber { def apply(value: Double): JValue = value match { case n if n.isNaN => JNull case n if n.isInfinity => JNull - case _ => new JNumber(value.toString) + case _ => new JNumber(value.toString)(NumberFlags.doubleConstructed) } /** @@ -77,12 +82,12 @@ object JNumber { def apply(value: Float): JValue = value match { case n if java.lang.Float.isNaN(n) => JNull case n if n.isInfinity => JNull - case _ => new JNumber(value.toString) + case _ => new JNumber(value.toString)(NumberFlags.floatConstructed) } def fromString(value: String): Option[JNumber] = value match { - case jNumberRegex(_ *) => Some(new JNumber(value)) + case jNumberRegex(_ *) => Some(new JNumber(value)(0)) case _ => None } @@ -97,15 +102,9 @@ object JNumber { */ // Due to a restriction in Scala 2.10, we cant override/replace the default apply method // generated by the compiler even when the constructor itself is marked private -final class JNumber private[ast] (val value: String) extends JValue { - - /** - * Javascript specification for numbers specify a [[scala.Double]], so this is the default export method to `Javascript` - * - * @param value - */ - def this(value: Double) = this(value.toString) - +final class JNumber private[ast] (val value: String)( + private[ast] val constructedFlag: Int) + extends JValue { override def toUnsafe: unsafe.JValue = unsafe.JNumber(value) override def toJsAny: js.Any = value.toDouble match { @@ -143,9 +142,79 @@ final class JNumber private[ast] (val value: String) extends JValue { def copy(value: String): JNumber = value match { - case jNumberRegex(_ *) => new JNumber(value) + case jNumberRegex(_ *) => new JNumber(value)(0) case _ => throw new NumberFormatException(value) } + + def toInt: Option[Long] = { + if ((constructedFlag & NumberFlags.int) == NumberFlags.int) + Some(value.toInt) + else { + try { + val asInt = value.toInt + if (BigInt(value) == BigInt(asInt)) + Some(asInt) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + + def toLong: Option[Long] = { + if ((constructedFlag & NumberFlags.long) == NumberFlags.long) + Some(value.toLong) + else { + try { + val asLong = value.toLong + if (BigInt(value) == BigInt(asLong)) + Some(asLong) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + + def toBigInt: Option[BigInt] = { + if ((constructedFlag & NumberFlags.bigInt) == NumberFlags.bigInt) + Some(BigInt(value)) + else { + try { + Some(BigInt(value)) + } catch { + case _: NumberFormatException => None + } + } + } + + def toBigDecimal: BigDecimal = BigDecimal(value) + + def toFloat: Option[Float] = { + if ((constructedFlag & NumberFlags.float) == NumberFlags.float) + Some(value.toFloat) + else { + val asFloat = value.toFloat + if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) + Some(asFloat) + else + None + } + } + + def toDouble: Option[Double] = { + if ((constructedFlag & NumberFlags.double) == NumberFlags.double) + Some(value.toDouble) + else { + val asDouble = value.toDouble + if (BigDecimal(value) == BigDecimal(asDouble)) + Some(asDouble) + else + None + } + } } /** Represents a JSON Boolean value, which can either be a diff --git a/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala b/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala index d1b7ca8..d22294e 100644 --- a/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala +++ b/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala @@ -56,21 +56,28 @@ final case class JString(value: String) extends JValue { } object JNumber { - def apply(value: Int): JNumber = JNumber(value.toString) + def apply(value: Int): JNumber = + JNumber(value.toString, NumberFlags.intConstructed) - def apply(value: Long): JNumber = JNumber(value.toString) + def apply(value: Long): JNumber = + JNumber(value.toString, NumberFlags.longConstructed) - def apply(value: BigInt): JNumber = JNumber(value.toString) + def apply(value: BigInt): JNumber = + JNumber(value.toString, NumberFlags.bigIntConstructed) - def apply(value: BigDecimal): JNumber = JNumber(value.toString) + def apply(value: BigDecimal): JNumber = + JNumber(value.toString, NumberFlags.bigDecimalConstructed) - def apply(value: Float): JNumber = JNumber(value.toString) + def apply(value: Float): JNumber = + JNumber(value.toString, NumberFlags.floatConstructed) - def apply(value: Double): JNumber = JNumber(value.toString) + def apply(value: Double): JNumber = + JNumber(value.toString, NumberFlags.doubleConstructed) - def apply(value: Integer): JNumber = JNumber(value.toString) + def apply(value: Integer): JNumber = + JNumber(value.toString, NumberFlags.intConstructed) - def apply(value: Array[Char]): JNumber = JNumber(new String(value)) + def apply(value: Array[Char]): JNumber = JNumber(new String(value), (0)) } /** Represents a JSON number value. @@ -84,10 +91,11 @@ object JNumber { * @author Matthew de Detrich */ // JNumber is internally represented as a string, to improve performance -final case class JNumber(value: String) extends JValue { +final case class JNumber(value: String, constructedFlag: Int = 0) + extends JValue { override def toStandard: ast.JValue = value match { - case jNumberRegex(_ *) => new ast.JNumber(value) + case jNumberRegex(_ *) => new ast.JNumber(value)(constructedFlag) case _ => throw new NumberFormatException(value) } @@ -100,6 +108,76 @@ final case class JNumber(value: String) extends JValue { case n if n.isInfinity => null case n => n } + + def toInt: Option[Long] = { + if ((constructedFlag & NumberFlags.int) == NumberFlags.int) + Some(value.toInt) + else { + try { + val asInt = value.toInt + if (BigInt(value) == BigInt(asInt)) + Some(asInt) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + + def toLong: Option[Long] = { + if ((constructedFlag & NumberFlags.long) == NumberFlags.long) + Some(value.toLong) + else { + try { + val asLong = value.toLong + if (BigInt(value) == BigInt(asLong)) + Some(asLong) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + + def toBigInt: Option[BigInt] = { + if ((constructedFlag & NumberFlags.bigInt) == NumberFlags.bigInt) + Some(BigInt(value)) + else { + try { + Some(BigInt(value)) + } catch { + case _: NumberFormatException => None + } + } + } + + def toBigDecimal: BigDecimal = BigDecimal(value) + + def toFloat: Option[Float] = { + if ((constructedFlag & NumberFlags.float) == NumberFlags.float) + Some(value.toFloat) + else { + val asFloat = value.toFloat + if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) + Some(asFloat) + else + None + } + } + + def toDouble: Option[Double] = { + if ((constructedFlag & NumberFlags.double) == NumberFlags.double) + Some(value.toDouble) + else { + val asDouble = value.toDouble + if (BigDecimal(value) == BigDecimal(asDouble)) + Some(asDouble) + else + None + } + } } /** Represents a JSON Boolean value, which can either be a @@ -232,7 +310,7 @@ final case class JObject(value: js.Array[JField] = js.Array()) extends JValue { case unsafe.JNull => unsafe.JNull.## case unsafe.JString(s) => s.## case unsafe.JBoolean(b) => b.## - case unsafe.JNumber(i) => i.## + case unsafe.JNumber(i, _) => i.## case unsafe.JArray(a) => a.## case unsafe.JObject(obj) => obj.## } @@ -304,7 +382,7 @@ final case class JArray(value: js.Array[JValue] = js.Array()) extends JValue { case unsafe.JNull => unsafe.JNull.## case unsafe.JString(s) => s.## case unsafe.JBoolean(b) => b.## - case unsafe.JNumber(i) => i.## + case unsafe.JNumber(i, _) => i.## case unsafe.JArray(a) => a.## case unsafe.JObject(obj) => obj.## } diff --git a/js/src/main/scala/scalajson/ast/JValue.scala b/js/src/main/scala/scalajson/ast/JValue.scala index 6d5eb47..5deeb6f 100644 --- a/js/src/main/scala/scalajson/ast/JValue.scala +++ b/js/src/main/scala/scalajson/ast/JValue.scala @@ -50,15 +50,20 @@ final case class JString(value: String) extends JValue { } object JNumber { - def apply(value: Int): JNumber = new JNumber(value.toString) + def apply(value: Int): JNumber = + new JNumber(value.toString)(NumberFlags.intConstructed) - def apply(value: Integer): JNumber = new JNumber(value.toString) + def apply(value: Integer): JNumber = + new JNumber(value.toString)(NumberFlags.intConstructed) - def apply(value: Long): JNumber = new JNumber(value.toString) + def apply(value: Long): JNumber = + new JNumber(value.toString)(NumberFlags.longConstructed) - def apply(value: BigInt): JNumber = new JNumber(value.toString()) + def apply(value: BigInt): JNumber = + new JNumber(value.toString())(NumberFlags.bigIntConstructed) - def apply(value: BigDecimal): JNumber = new JNumber(value.toString()) + def apply(value: BigDecimal): JNumber = + new JNumber(value.toString())(NumberFlags.bigDecimalConstructed) /** * @param value @@ -67,7 +72,7 @@ object JNumber { def apply(value: Double): JValue = value match { case n if n.isNaN => JNull case n if n.isInfinity => JNull - case _ => new JNumber(value.toString) + case _ => new JNumber(value.toString)(NumberFlags.doubleConstructed) } /** @@ -77,7 +82,7 @@ object JNumber { def apply(value: Float): JValue = value match { case n if java.lang.Float.isNaN(n) => JNull case n if n.isInfinity => JNull - case _ => new JNumber(value.toString) + case _ => new JNumber(value.toString)(NumberFlags.floatConstructed) } def apply(value: String): Option[JNumber] = @@ -85,7 +90,7 @@ object JNumber { def fromString(value: String): Option[JNumber] = value match { - case jNumberRegex(_ *) => Some(new JNumber(value)) + case jNumberRegex(_ *) => Some(new JNumber(value)(0)) case _ => None } } @@ -96,16 +101,19 @@ object JNumber { * * @author Matthew de Detrich */ -final case class JNumber private[ast] (value: String) extends JValue { +final case class JNumber private[ast] (value: String)( + private[ast] val constructedFlag: Int) + extends JValue { /** * Javascript specification for numbers specify a [[scala.Double]], so this is the default export method to `Javascript` * * @param value */ - def this(value: Double) = this(value.toString) + def this(value: Double) = this(value.toString)(NumberFlags.doubleConstructed) - override def toUnsafe: unsafe.JValue = unsafe.JNumber(value) + override def toUnsafe: unsafe.JValue = + new unsafe.JNumber(value, constructedFlag) override def toJsAny: js.Any = value.toDouble match { case n if n.isNaN => null @@ -125,9 +133,79 @@ final case class JNumber private[ast] (value: String) extends JValue { def copy(value: String): JNumber = value match { - case jNumberRegex(_ *) => new JNumber(value) + case jNumberRegex(_ *) => new JNumber(value)(0) case _ => throw new NumberFormatException(value) } + + def toInt: Option[Long] = { + if ((constructedFlag & NumberFlags.int) == NumberFlags.int) + Some(value.toInt) + else { + try { + val asInt = value.toInt + if (BigInt(value) == BigInt(asInt)) + Some(asInt) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + + def toLong: Option[Long] = { + if ((constructedFlag & NumberFlags.long) == NumberFlags.long) + Some(value.toLong) + else { + try { + val asLong = value.toLong + if (BigInt(value) == BigInt(asLong)) + Some(asLong) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + + def toBigInt: Option[BigInt] = { + if ((constructedFlag & NumberFlags.bigInt) == NumberFlags.bigInt) + Some(BigInt(value)) + else { + try { + Some(BigInt(value)) + } catch { + case _: NumberFormatException => None + } + } + } + + def toBigDecimal: BigDecimal = BigDecimal(value) + + def toFloat: Option[Float] = { + if ((constructedFlag & NumberFlags.float) == NumberFlags.float) + Some(value.toFloat) + else { + val asFloat = value.toFloat + if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) + Some(asFloat) + else + None + } + } + + def toDouble: Option[Double] = { + if ((constructedFlag & NumberFlags.double) == NumberFlags.double) + Some(value.toDouble) + else { + val asDouble = value.toDouble + if (BigDecimal(value) == BigDecimal(asDouble)) + Some(asDouble) + else + None + } + } } /** Represents a JSON Boolean value, which can either be a diff --git a/js/src/main/scala/scalajson/ast/unsafe/JValue.scala b/js/src/main/scala/scalajson/ast/unsafe/JValue.scala index 9ea4882..d63dd0a 100644 --- a/js/src/main/scala/scalajson/ast/unsafe/JValue.scala +++ b/js/src/main/scala/scalajson/ast/unsafe/JValue.scala @@ -54,21 +54,28 @@ final case class JString(value: String) extends JValue { } object JNumber { - def apply(value: Int): JNumber = JNumber(value.toString) + def apply(value: Int): JNumber = + new JNumber(value.toString, NumberFlags.intConstructed) - def apply(value: Long): JNumber = JNumber(value.toString) + def apply(value: Long): JNumber = + new JNumber(value.toString, NumberFlags.longConstructed) - def apply(value: BigInt): JNumber = JNumber(value.toString) + def apply(value: BigInt): JNumber = + new JNumber(value.toString, NumberFlags.bigIntConstructed) - def apply(value: BigDecimal): JNumber = JNumber(value.toString) + def apply(value: BigDecimal): JNumber = + new JNumber(value.toString, NumberFlags.bigDecimalConstructed) - def apply(value: Float): JNumber = JNumber(value.toString) + def apply(value: Float): JNumber = + new JNumber(value.toString, NumberFlags.floatConstructed) - def apply(value: Double): JNumber = JNumber(value.toString) + def apply(value: Double): JNumber = + new JNumber(value.toString, NumberFlags.doubleConstructed) - def apply(value: Integer): JNumber = JNumber(value.toString) + def apply(value: Integer): JNumber = + new JNumber(value.toString, NumberFlags.intConstructed) - def apply(value: Array[Char]): JNumber = JNumber(new String(value)) + def apply(value: Array[Char]): JNumber = new JNumber(new String(value), 0) } /** Represents a JSON number value. @@ -82,22 +89,89 @@ object JNumber { * @author Matthew de Detrich */ // JNumber is internally represented as a string, to improve performance -final case class JNumber(value: String) extends JValue { +final case class JNumber(value: String, constructedFlag: Int = 0) + extends JValue { override def toStandard: ast.JValue = value match { - case jNumberRegex(_ *) => new ast.JNumber(value) + case jNumberRegex(_ *) => new ast.JNumber(value)(constructedFlag) case _ => throw new NumberFormatException(value) } - def this(value: Double) = { - this(value.toString) - } - override def toJsAny: js.Any = value.toDouble match { case n if n.isNaN => null case n if n.isInfinity => null case n => n } + + def toInt: Option[Long] = { + if ((constructedFlag & NumberFlags.int) == NumberFlags.int) + Some(value.toInt) + else { + try { + val asInt = value.toInt + if (BigInt(value) == BigInt(asInt)) + Some(asInt) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + + def toLong: Option[Long] = { + if ((constructedFlag & NumberFlags.long) == NumberFlags.long) + Some(value.toLong) + else { + try { + val asLong = value.toLong + if (BigInt(value) == BigInt(asLong)) + Some(asLong) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + + def toBigInt: Option[BigInt] = { + if ((constructedFlag & NumberFlags.bigInt) == NumberFlags.bigInt) + Some(BigInt(value)) + else { + try { + Some(BigInt(value)) + } catch { + case _: NumberFormatException => None + } + } + } + + def toBigDecimal: BigDecimal = BigDecimal(value) + + def toFloat: Option[Float] = { + if ((constructedFlag & NumberFlags.float) == NumberFlags.float) + Some(value.toFloat) + else { + val asFloat = value.toFloat + if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) + Some(asFloat) + else + None + } + } + + def toDouble: Option[Double] = { + if ((constructedFlag & NumberFlags.double) == NumberFlags.double) + Some(value.toDouble) + else { + val asDouble = value.toDouble + if (BigDecimal(value) == BigDecimal(asDouble)) + Some(asDouble) + else + None + } + } } /** Represents a JSON Boolean value, which can either be a @@ -231,7 +305,7 @@ final case class JObject(value: js.Array[JField] = js.Array()) extends JValue { case unsafe.JNull => unsafe.JNull.## case unsafe.JString(s) => s.## case unsafe.JBoolean(b) => b.## - case unsafe.JNumber(i) => i.## + case unsafe.JNumber(i, _) => i.## case unsafe.JArray(a) => a.## case unsafe.JObject(obj) => obj.## } @@ -303,7 +377,7 @@ final case class JArray(value: js.Array[JValue] = js.Array()) extends JValue { case unsafe.JNull => unsafe.JNull.## case unsafe.JString(s) => s.## case unsafe.JBoolean(b) => b.## - case unsafe.JNumber(i) => i.## + case unsafe.JNumber(i, _) => i.## case unsafe.JArray(a) => a.## case unsafe.JObject(obj) => obj.## } diff --git a/jvm/src/main/scala-2.10/scalajson.ast/JValue.scala b/jvm/src/main/scala-2.10/scalajson.ast/JValue.scala index d65381f..2b8e4e4 100644 --- a/jvm/src/main/scala-2.10/scalajson.ast/JValue.scala +++ b/jvm/src/main/scala-2.10/scalajson.ast/JValue.scala @@ -40,11 +40,14 @@ final case class JString(value: String) extends JValue { * return a JNull */ object JNumber { - def apply(value: Int): JNumber = new JNumber(value.toString) + def apply(value: Int): JNumber = + new JNumber(value.toString)(NumberFlags.intConstructed) - def apply(value: Long): JNumber = new JNumber(value.toString) + def apply(value: Long): JNumber = + new JNumber(value.toString)(NumberFlags.longConstructed) - def apply(value: BigInt): JNumber = new JNumber(value.toString) + def apply(value: BigInt): JNumber = + new JNumber(value.toString)(NumberFlags.bigIntConstructed) /** * @param value @@ -53,10 +56,11 @@ object JNumber { def apply(value: Float): JValue = value match { case n if java.lang.Float.isNaN(n) => JNull case n if n.isInfinity => JNull - case _ => new JNumber(value.toString) + case _ => new JNumber(value.toString)(NumberFlags.floatConstructed) } - def apply(value: BigDecimal): JNumber = new JNumber(value.toString()) + def apply(value: BigDecimal): JNumber = + new JNumber(value.toString())(NumberFlags.bigDecimalConstructed) /** * @param value @@ -65,17 +69,18 @@ object JNumber { def apply(value: Double): JValue = value match { case n if n.isNaN => JNull case n if n.isInfinity => JNull - case _ => new JNumber(value.toString) + case _ => new JNumber(value.toString)(NumberFlags.doubleConstructed) } - def apply(value: Integer): JNumber = new JNumber(value.toString) + def apply(value: Integer): JNumber = + new JNumber(value.toString)(NumberFlags.intConstructed) def apply(value: Array[Char]): Option[JNumber] = fromString(new String(value)) def fromString(value: String): Option[JNumber] = value match { - case jNumberRegex(_ *) => Some(new JNumber(value)) + case jNumberRegex(_ *) => Some(new JNumber(value)(0)) case _ => None } @@ -90,8 +95,11 @@ object JNumber { */ // Due to a restriction in Scala 2.10, we cant override/replace the default apply method // generated by the compiler even when the constructor itself is marked private -final class JNumber private[ast] (val value: String) extends JValue { - override def toUnsafe: unsafe.JValue = unsafe.JNumber(value) +final class JNumber private[ast] (val value: String)( + private val constructedFlag: Int) + extends JValue { + override def toUnsafe: unsafe.JValue = + new unsafe.JNumber(value, constructedFlag) override def equals(obj: Any): Boolean = obj match { @@ -122,9 +130,79 @@ final class JNumber private[ast] (val value: String) extends JValue { def copy(value: String): JNumber = value match { - case jNumberRegex(_ *) => new JNumber(value) + case jNumberRegex(_ *) => new JNumber(value)(0) case _ => throw new NumberFormatException(value) } + + def toInt: Option[Long] = { + if ((constructedFlag & NumberFlags.int) == NumberFlags.int) + Some(value.toInt) + else { + try { + val asInt = value.toInt + if (BigInt(value) == BigInt(asInt)) + Some(asInt) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + + def toLong: Option[Long] = { + if ((constructedFlag & NumberFlags.long) == NumberFlags.long) + Some(value.toLong) + else { + try { + val asLong = value.toLong + if (BigInt(value) == BigInt(asLong)) + Some(asLong) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + + def toBigInt: Option[BigInt] = { + if ((constructedFlag & NumberFlags.bigInt) == NumberFlags.bigInt) + Some(BigInt(value)) + else { + try { + Some(BigInt(value)) + } catch { + case _: NumberFormatException => None + } + } + } + + def toBigDecimal: BigDecimal = BigDecimal(value) + + def toFloat: Option[Float] = { + if ((constructedFlag & NumberFlags.float) == NumberFlags.float) + Some(value.toFloat) + else { + val asFloat = value.toFloat + if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) + Some(asFloat) + else + None + } + } + + def toDouble: Option[Double] = { + if ((constructedFlag & NumberFlags.double) == NumberFlags.double) + Some(value.toDouble) + else { + val asDouble = value.toDouble + if (BigDecimal(value) == BigDecimal(asDouble)) + Some(asDouble) + else + None + } + } } /** Represents a JSON Boolean value, which can either be a diff --git a/jvm/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala b/jvm/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala index 3437473..7d906f6 100644 --- a/jvm/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala +++ b/jvm/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala @@ -42,21 +42,30 @@ final case class JString(value: String) extends JValue { } object JNumber { - def apply(value: Int): JNumber = JNumber(value.toInt.toString) + def apply(value: Int): JNumber = + new JNumber(value.toInt.toString, NumberFlags.intConstructed) - def apply(value: Long): JNumber = JNumber(value.toString) + def apply(value: Long): JNumber = + new JNumber(value.toString, NumberFlags.longConstructed) - def apply(value: BigInt): JNumber = JNumber(value.toString) + def apply(value: BigInt): JNumber = + new JNumber(value.toString, NumberFlags.bigIntConstructed) - def apply(value: BigDecimal): JNumber = JNumber(value.toString) + def apply(value: BigDecimal): JNumber = + new JNumber(value.toString, NumberFlags.bigDecimalConstructed) - def apply(value: Float): JNumber = JNumber(value.toString) + def apply(value: Float): JNumber = + new JNumber(value.toString, NumberFlags.floatConstructed) - def apply(value: Double): JNumber = JNumber(value.toString) + def apply(value: Double): JNumber = + new JNumber(value.toString, NumberFlags.doubleConstructed) - def apply(value: Integer): JNumber = JNumber(value.toString) + def apply(value: Integer): JNumber = + new JNumber(value.toString, NumberFlags.intConstructed) - def apply(value: Array[Char]): JNumber = JNumber(new String(value)) + def apply(value: Array[Char]): JNumber = new JNumber(new String(value), 0) + + def apply(value: String): JNumber = new JNumber(value, 0) } /** Represents a JSON number value. @@ -70,12 +79,83 @@ object JNumber { * @author Matthew de Detrich */ // JNumber is internally represented as a string, to improve performance -final case class JNumber(value: String) extends JValue { +final case class JNumber(value: String, constructedFlag: Int = 0) + extends JValue { override def toStandard: ast.JValue = value match { - case jNumberRegex(_ *) => new ast.JNumber(value) + case jNumberRegex(_ *) => new ast.JNumber(value)(constructedFlag) case _ => throw new NumberFormatException(value) } + + def toInt: Option[Long] = { + if ((constructedFlag & NumberFlags.int) == NumberFlags.int) + Some(value.toInt) + else { + try { + val asInt = value.toInt + if (BigInt(value) == BigInt(asInt)) + Some(asInt) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + + def toLong: Option[Long] = { + if ((constructedFlag & NumberFlags.long) == NumberFlags.long) + Some(value.toLong) + else { + try { + val asLong = value.toLong + if (BigInt(value) == BigInt(asLong)) + Some(asLong) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + + def toBigInt: Option[BigInt] = { + if ((constructedFlag & NumberFlags.bigInt) == NumberFlags.bigInt) + Some(BigInt(value)) + else { + try { + Some(BigInt(value)) + } catch { + case _: NumberFormatException => None + } + } + } + + def toBigDecimal: BigDecimal = BigDecimal(value) + + def toFloat: Option[Float] = { + if ((constructedFlag & NumberFlags.float) == NumberFlags.float) + Some(value.toFloat) + else { + val asFloat = value.toFloat + if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) + Some(asFloat) + else + None + } + } + + def toDouble: Option[Double] = { + if ((constructedFlag & NumberFlags.double) == NumberFlags.double) + Some(value.toDouble) + else { + val asDouble = value.toDouble + if (BigDecimal(value) == BigDecimal(asDouble)) + Some(asDouble) + else + None + } + } } /** Represents a JSON Boolean value, which can either be a diff --git a/jvm/src/main/scala/scalajson/ast/JValue.scala b/jvm/src/main/scala/scalajson/ast/JValue.scala index eafbe62..e8956d3 100644 --- a/jvm/src/main/scala/scalajson/ast/JValue.scala +++ b/jvm/src/main/scala/scalajson/ast/JValue.scala @@ -40,11 +40,14 @@ final case class JString(value: String) extends JValue { * return a JNull */ object JNumber { - def apply(value: Int): JNumber = new JNumber(value.toString) + def apply(value: Int): JNumber = + new JNumber(value.toString)(NumberFlags.intConstructed) - def apply(value: Long): JNumber = new JNumber(value.toString) + def apply(value: Long): JNumber = + new JNumber(value.toString)(NumberFlags.longConstructed) - def apply(value: BigInt): JNumber = new JNumber(value.toString) + def apply(value: BigInt): JNumber = + new JNumber(value.toString)(NumberFlags.bigIntConstructed) /** * @param value @@ -53,10 +56,11 @@ object JNumber { def apply(value: Float): JValue = value match { case n if java.lang.Float.isNaN(n) => JNull case n if n.isInfinity => JNull - case _ => new JNumber(value.toString) + case _ => new JNumber(value.toString)(NumberFlags.floatConstructed) } - def apply(value: BigDecimal): JNumber = new JNumber(value.toString()) + def apply(value: BigDecimal): JNumber = + new JNumber(value.toString())(NumberFlags.bigDecimalConstructed) /** * @param value @@ -65,10 +69,11 @@ object JNumber { def apply(value: Double): JValue = value match { case n if n.isNaN => JNull case n if n.isInfinity => JNull - case _ => new JNumber(value.toString) + case _ => new JNumber(value.toString)(NumberFlags.doubleConstructed) } - def apply(value: Integer): JNumber = new JNumber(value.toString) + def apply(value: Integer): JNumber = + new JNumber(value.toString)(NumberFlags.intConstructed) def apply(value: Array[Char]): Option[JNumber] = fromString(new String(value)) @@ -77,7 +82,7 @@ object JNumber { def fromString(value: String): Option[JNumber] = value match { - case jNumberRegex(_ *) => Some(new JNumber(value)) + case jNumberRegex(_ *) => Some(new JNumber(value)(0)) case _ => None } } @@ -88,8 +93,11 @@ object JNumber { * * @author Matthew de Detrich */ -final case class JNumber private[ast] (value: String) extends JValue { - override def toUnsafe: unsafe.JValue = unsafe.JNumber(value) +final case class JNumber private[ast] (value: String)( + private[ast] val constructedFlag: Int) + extends JValue { + override def toUnsafe: unsafe.JValue = + new unsafe.JNumber(value, constructedFlag) override def equals(obj: Any): Boolean = obj match { @@ -103,9 +111,79 @@ final case class JNumber private[ast] (value: String) extends JValue { def copy(value: String): JNumber = value match { - case jNumberRegex(_ *) => new JNumber(value) + case jNumberRegex(_ *) => new JNumber(value)(0) case _ => throw new NumberFormatException(value) } + + def toInt: Option[Long] = { + if ((constructedFlag & NumberFlags.int) == NumberFlags.int) + Some(value.toInt) + else { + try { + val asInt = value.toInt + if (BigInt(value) == BigInt(asInt)) + Some(asInt) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + + def toLong: Option[Long] = { + if ((constructedFlag & NumberFlags.long) == NumberFlags.long) + Some(value.toLong) + else { + try { + val asLong = value.toLong + if (BigInt(value) == BigInt(asLong)) + Some(asLong) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + + def toBigInt: Option[BigInt] = { + if ((constructedFlag & NumberFlags.bigInt) == NumberFlags.bigInt) + Some(BigInt(value)) + else { + try { + Some(BigInt(value)) + } catch { + case _: NumberFormatException => None + } + } + } + + def toBigDecimal: BigDecimal = BigDecimal(value) + + def toFloat: Option[Float] = { + if ((constructedFlag & NumberFlags.float) == NumberFlags.float) + Some(value.toFloat) + else { + val asFloat = value.toFloat + if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) + Some(asFloat) + else + None + } + } + + def toDouble: Option[Double] = { + if ((constructedFlag & NumberFlags.double) == NumberFlags.double) + Some(value.toDouble) + else { + val asDouble = value.toDouble + if (BigDecimal(value) == BigDecimal(asDouble)) + Some(asDouble) + else + None + } + } } /** Represents a JSON Boolean value, which can either be a diff --git a/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala b/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala index 435451c..b7667ce 100644 --- a/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala +++ b/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala @@ -42,21 +42,30 @@ final case class JString(value: String) extends JValue { } object JNumber { - def apply(value: Int): JNumber = JNumber(value.toInt.toString) + def apply(value: Int): JNumber = + new JNumber(value.toInt.toString, NumberFlags.intConstructed) - def apply(value: Long): JNumber = JNumber(value.toString) + def apply(value: Long): JNumber = + new JNumber(value.toString, NumberFlags.longConstructed) - def apply(value: BigInt): JNumber = JNumber(value.toString) + def apply(value: BigInt): JNumber = + new JNumber(value.toString, NumberFlags.bigIntConstructed) - def apply(value: BigDecimal): JNumber = JNumber(value.toString) + def apply(value: BigDecimal): JNumber = + new JNumber(value.toString, NumberFlags.bigDecimalConstructed) - def apply(value: Float): JNumber = JNumber(value.toString) + def apply(value: Float): JNumber = + new JNumber(value.toString, NumberFlags.floatConstructed) - def apply(value: Double): JNumber = JNumber(value.toString) + def apply(value: Double): JNumber = + new JNumber(value.toString, NumberFlags.doubleConstructed) - def apply(value: Integer): JNumber = JNumber(value.toString) + def apply(value: Integer): JNumber = + new JNumber(value.toString, NumberFlags.intConstructed) - def apply(value: Array[Char]): JNumber = JNumber(new String(value)) + def apply(value: Array[Char]): JNumber = new JNumber(new String(value), 0) + + def unapply(arg: JNumber): Option[String] = Some(arg.value) } /** Represents a JSON number value. @@ -70,12 +79,86 @@ object JNumber { * @author Matthew de Detrich */ // JNumber is internally represented as a string, to improve performance -final case class JNumber(value: String) extends JValue { +final case class JNumber(value: String, constructedFlag: Int = 0) + extends JValue { + def isEmpty: Boolean = false + def get: String = value + override def toStandard: ast.JValue = value match { - case jNumberRegex(_ *) => new ast.JNumber(value) + case jNumberRegex(_ *) => new ast.JNumber(value)(constructedFlag) case _ => throw new NumberFormatException(value) } + + def toInt: Option[Long] = { + if ((constructedFlag & NumberFlags.int) == NumberFlags.int) + Some(value.toInt) + else { + try { + val asInt = value.toInt + if (BigInt(value) == BigInt(asInt)) + Some(asInt) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + + def toLong: Option[Long] = { + if ((constructedFlag & NumberFlags.long) == NumberFlags.long) + Some(value.toLong) + else { + try { + val asLong = value.toLong + if (BigInt(value) == BigInt(asLong)) + Some(asLong) + else + None + } catch { + case _: NumberFormatException => None + } + } + } + + def toBigInt: Option[BigInt] = { + if ((constructedFlag & NumberFlags.bigInt) == NumberFlags.bigInt) + Some(BigInt(value)) + else { + try { + Some(BigInt(value)) + } catch { + case _: NumberFormatException => None + } + } + } + + def toBigDecimal: BigDecimal = BigDecimal(value) + + def toFloat: Option[Float] = { + if ((constructedFlag & NumberFlags.float) == NumberFlags.float) + Some(value.toFloat) + else { + val asFloat = value.toFloat + if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) + Some(asFloat) + else + None + } + } + + def toDouble: Option[Double] = { + if ((constructedFlag & NumberFlags.double) == NumberFlags.double) + Some(value.toDouble) + else { + val asDouble = value.toDouble + if (BigDecimal(value) == BigDecimal(asDouble)) + Some(asDouble) + else + None + } + } } /** Represents a JSON Boolean value, which can either be a diff --git a/shared/src/main/scala-2.10/scalajson.ast/package.scala b/shared/src/main/scala-2.10/scalajson.ast/package.scala index 3f6f7b3..8e56b38 100644 --- a/shared/src/main/scala-2.10/scalajson.ast/package.scala +++ b/shared/src/main/scala-2.10/scalajson.ast/package.scala @@ -4,6 +4,27 @@ import scala.util.matching.Regex package object ast { + // Bit flags that are used for storing how a number was constructed + + object NumberFlags { + @inline private[ast] final def int: Int = 1 + @inline private[ast] final def long: Int = 2 + @inline private[ast] final def bigInt: Int = 4 + @inline private[ast] final def bigDecimal: Int = 8 + @inline private[ast] final def float: Int = 16 + @inline private[ast] final def double: Int = 32 + + @inline private[ast] final val intConstructed + : Int = int | long | bigInt | bigDecimal + @inline private[ast] final val longConstructed + : Int = long | bigInt | bigDecimal + @inline private[ast] final val bigIntConstructed: Int = bigInt + @inline private[ast] final val bigDecimalConstructed: Int = bigDecimal + @inline private[ast] final val floatConstructed + : Int = float | double | bigDecimal + @inline private[ast] final val doubleConstructed: Int = double | bigDecimal + } + /** * A regex that will match any valid JSON number for unlimited * precision diff --git a/shared/src/main/scala/scalajson/ast/package.scala b/shared/src/main/scala/scalajson/ast/package.scala index 3f6f7b3..8e56b38 100644 --- a/shared/src/main/scala/scalajson/ast/package.scala +++ b/shared/src/main/scala/scalajson/ast/package.scala @@ -4,6 +4,27 @@ import scala.util.matching.Regex package object ast { + // Bit flags that are used for storing how a number was constructed + + object NumberFlags { + @inline private[ast] final def int: Int = 1 + @inline private[ast] final def long: Int = 2 + @inline private[ast] final def bigInt: Int = 4 + @inline private[ast] final def bigDecimal: Int = 8 + @inline private[ast] final def float: Int = 16 + @inline private[ast] final def double: Int = 32 + + @inline private[ast] final val intConstructed + : Int = int | long | bigInt | bigDecimal + @inline private[ast] final val longConstructed + : Int = long | bigInt | bigDecimal + @inline private[ast] final val bigIntConstructed: Int = bigInt + @inline private[ast] final val bigDecimalConstructed: Int = bigDecimal + @inline private[ast] final val floatConstructed + : Int = float | double | bigDecimal + @inline private[ast] final val doubleConstructed: Int = double | bigDecimal + } + /** * A regex that will match any valid JSON number for unlimited * precision diff --git a/shared/src/test/scala/specs/unsafe/Generators.scala b/shared/src/test/scala/specs/unsafe/Generators.scala index badbad7..dd43683 100644 --- a/shared/src/test/scala/specs/unsafe/Generators.scala +++ b/shared/src/test/scala/specs/unsafe/Generators.scala @@ -189,7 +189,7 @@ object Generators { case obj: scalajson.ast.unsafe.JObject => shrink(obj) case scalajson.ast.unsafe.JString(str) => shrink(str) map scalajson.ast.unsafe.JString - case scalajson.ast.unsafe.JNumber(num) => + case scalajson.ast.unsafe.JNumber(num, _) => shrink(num) map (x => scalajson.ast.unsafe.JNumber(x)) case scalajson.ast.unsafe.JNull | scalajson.ast.unsafe.JBoolean(_) => Stream.empty[scalajson.ast.unsafe.JValue] From 8c5b1bee6da1347e2f2e55736d1d8aead6bdc6d9 Mon Sep 17 00:00:00 2001 From: Matthew de Detrich Date: Tue, 11 Jul 2017 22:53:15 +0200 Subject: [PATCH 2/5] Fixed up some of the checks #37 --- .../ast/JNumberConversionBenchmark.scala | 28 +++++++++----- .../scala-2.10/scalajson.ast/JValue.scala | 8 +++- .../scalajson.ast/unsafe/JValue.scala | 36 ++++++++++++------ js/src/main/scala/scalajson/ast/JValue.scala | 36 ++++++++++++------ .../scala/scalajson/ast/unsafe/JValue.scala | 37 +++++++++++++------ .../scala-2.10/scalajson.ast/JValue.scala | 36 ++++++++++++------ .../scalajson.ast/unsafe/JValue.scala | 37 +++++++++++++------ jvm/src/main/scala/scalajson/ast/JValue.scala | 36 ++++++++++++------ .../scala/scalajson/ast/unsafe/JValue.scala | 8 +++- 9 files changed, 185 insertions(+), 77 deletions(-) diff --git a/benchmark/jvm/src/test/scala/scalajson/ast/JNumberConversionBenchmark.scala b/benchmark/jvm/src/test/scala/scalajson/ast/JNumberConversionBenchmark.scala index 9cae717..11f11b2 100644 --- a/benchmark/jvm/src/test/scala/scalajson/ast/JNumberConversionBenchmark.scala +++ b/benchmark/jvm/src/test/scala/scalajson/ast/JNumberConversionBenchmark.scala @@ -58,22 +58,32 @@ object JNumberConversionBenchmark extends Bench.ForkedTime { if ((floatConstructedFlag & NumberFlags.float) == NumberFlags.float) Some(value.toFloat) else { + try { + val asFloat = value.toFloat + if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) + Some(asFloat) + else + None + } catch { + case _: NumberFormatException => None + } + + } + } + } + + performance of "floatManualCheckSuccess" in { + using(floatString) in { value: String => + try { val asFloat = value.toFloat if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) Some(asFloat) else None + } catch { + case _: NumberFormatException => None } - } - } - performance of "floatManualCheckSuccess" in { - using(floatString) in { value: String => - val asFloat = value.toFloat - if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) - Some(asFloat) - else - None } } } diff --git a/js/src/main/scala-2.10/scalajson.ast/JValue.scala b/js/src/main/scala-2.10/scalajson.ast/JValue.scala index 844d296..5383e2c 100644 --- a/js/src/main/scala-2.10/scalajson.ast/JValue.scala +++ b/js/src/main/scala-2.10/scalajson.ast/JValue.scala @@ -190,7 +190,13 @@ final class JNumber private[ast] (val value: String)( } } - def toBigDecimal: BigDecimal = BigDecimal(value) + def toBigDecimal: Option[BigDecimal] = { + try { + Some(BigDecimal(value)) + } catch { + case _: NumberFormatException => None + } + } def toFloat: Option[Float] = { if ((constructedFlag & NumberFlags.float) == NumberFlags.float) diff --git a/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala b/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala index d22294e..030a8c9 100644 --- a/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala +++ b/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala @@ -153,17 +153,27 @@ final case class JNumber(value: String, constructedFlag: Int = 0) } } - def toBigDecimal: BigDecimal = BigDecimal(value) + def toBigDecimal: Option[BigDecimal] = { + try { + Some(BigDecimal(value)) + } catch { + case _: NumberFormatException => None + } + } def toFloat: Option[Float] = { if ((constructedFlag & NumberFlags.float) == NumberFlags.float) Some(value.toFloat) else { - val asFloat = value.toFloat - if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) - Some(asFloat) - else - None + try { + val asFloat = value.toFloat + if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) + Some(asFloat) + else + None + } catch { + case _: NumberFormatException => None + } } } @@ -171,11 +181,15 @@ final case class JNumber(value: String, constructedFlag: Int = 0) if ((constructedFlag & NumberFlags.double) == NumberFlags.double) Some(value.toDouble) else { - val asDouble = value.toDouble - if (BigDecimal(value) == BigDecimal(asDouble)) - Some(asDouble) - else - None + try { + val asDouble = value.toDouble + if (BigDecimal(value) == BigDecimal(asDouble)) + Some(asDouble) + else + None + } catch { + case _: NumberFormatException => None + } } } } diff --git a/js/src/main/scala/scalajson/ast/JValue.scala b/js/src/main/scala/scalajson/ast/JValue.scala index 5deeb6f..0436922 100644 --- a/js/src/main/scala/scalajson/ast/JValue.scala +++ b/js/src/main/scala/scalajson/ast/JValue.scala @@ -181,17 +181,27 @@ final case class JNumber private[ast] (value: String)( } } - def toBigDecimal: BigDecimal = BigDecimal(value) + def toBigDecimal: Option[BigDecimal] = { + try { + Some(BigDecimal(value)) + } catch { + case _: NumberFormatException => None + } + } def toFloat: Option[Float] = { if ((constructedFlag & NumberFlags.float) == NumberFlags.float) Some(value.toFloat) else { - val asFloat = value.toFloat - if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) - Some(asFloat) - else - None + try { + val asFloat = value.toFloat + if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) + Some(asFloat) + else + None + } catch { + case _: NumberFormatException => None + } } } @@ -199,11 +209,15 @@ final case class JNumber private[ast] (value: String)( if ((constructedFlag & NumberFlags.double) == NumberFlags.double) Some(value.toDouble) else { - val asDouble = value.toDouble - if (BigDecimal(value) == BigDecimal(asDouble)) - Some(asDouble) - else - None + try { + val asDouble = value.toDouble + if (BigDecimal(value) == BigDecimal(asDouble)) + Some(asDouble) + else + None + } catch { + case _: NumberFormatException => None + } } } } diff --git a/js/src/main/scala/scalajson/ast/unsafe/JValue.scala b/js/src/main/scala/scalajson/ast/unsafe/JValue.scala index d63dd0a..d5308ff 100644 --- a/js/src/main/scala/scalajson/ast/unsafe/JValue.scala +++ b/js/src/main/scala/scalajson/ast/unsafe/JValue.scala @@ -147,17 +147,27 @@ final case class JNumber(value: String, constructedFlag: Int = 0) } } - def toBigDecimal: BigDecimal = BigDecimal(value) + def toBigDecimal: Option[BigDecimal] = { + try { + Some(BigDecimal(value)) + } catch { + case _: NumberFormatException => None + } + } def toFloat: Option[Float] = { if ((constructedFlag & NumberFlags.float) == NumberFlags.float) Some(value.toFloat) else { - val asFloat = value.toFloat - if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) - Some(asFloat) - else - None + try { + val asFloat = value.toFloat + if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) + Some(asFloat) + else + None + } catch { + case _: NumberFormatException => None + } } } @@ -165,11 +175,16 @@ final case class JNumber(value: String, constructedFlag: Int = 0) if ((constructedFlag & NumberFlags.double) == NumberFlags.double) Some(value.toDouble) else { - val asDouble = value.toDouble - if (BigDecimal(value) == BigDecimal(asDouble)) - Some(asDouble) - else - None + try { + val asDouble = value.toDouble + if (BigDecimal(value) == BigDecimal(asDouble)) + Some(asDouble) + else + None + } catch { + case _: NumberFormatException => None + } + } } } diff --git a/jvm/src/main/scala-2.10/scalajson.ast/JValue.scala b/jvm/src/main/scala-2.10/scalajson.ast/JValue.scala index 2b8e4e4..f54dd6c 100644 --- a/jvm/src/main/scala-2.10/scalajson.ast/JValue.scala +++ b/jvm/src/main/scala-2.10/scalajson.ast/JValue.scala @@ -178,17 +178,27 @@ final class JNumber private[ast] (val value: String)( } } - def toBigDecimal: BigDecimal = BigDecimal(value) + def toBigDecimal: Option[BigDecimal] = { + try { + Some(BigDecimal(value)) + } catch { + case _: NumberFormatException => None + } + } def toFloat: Option[Float] = { if ((constructedFlag & NumberFlags.float) == NumberFlags.float) Some(value.toFloat) else { - val asFloat = value.toFloat - if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) - Some(asFloat) - else - None + try { + val asFloat = value.toFloat + if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) + Some(asFloat) + else + None + } catch { + case _: NumberFormatException => None + } } } @@ -196,11 +206,15 @@ final class JNumber private[ast] (val value: String)( if ((constructedFlag & NumberFlags.double) == NumberFlags.double) Some(value.toDouble) else { - val asDouble = value.toDouble - if (BigDecimal(value) == BigDecimal(asDouble)) - Some(asDouble) - else - None + try { + val asDouble = value.toDouble + if (BigDecimal(value) == BigDecimal(asDouble)) + Some(asDouble) + else + None + } catch { + case _: NumberFormatException => None + } } } } diff --git a/jvm/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala b/jvm/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala index 7d906f6..ab2738d 100644 --- a/jvm/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala +++ b/jvm/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala @@ -131,17 +131,28 @@ final case class JNumber(value: String, constructedFlag: Int = 0) } } - def toBigDecimal: BigDecimal = BigDecimal(value) + def toBigDecimal: Option[BigDecimal] = { + try { + Some(BigDecimal(value)) + } catch { + case _: NumberFormatException => None + } + } def toFloat: Option[Float] = { if ((constructedFlag & NumberFlags.float) == NumberFlags.float) Some(value.toFloat) else { - val asFloat = value.toFloat - if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) - Some(asFloat) - else - None + try { + val asFloat = value.toFloat + if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) + Some(asFloat) + else + None + } catch { + case _: NumberFormatException => None + } + } } @@ -149,11 +160,15 @@ final case class JNumber(value: String, constructedFlag: Int = 0) if ((constructedFlag & NumberFlags.double) == NumberFlags.double) Some(value.toDouble) else { - val asDouble = value.toDouble - if (BigDecimal(value) == BigDecimal(asDouble)) - Some(asDouble) - else - None + try { + val asDouble = value.toDouble + if (BigDecimal(value) == BigDecimal(asDouble)) + Some(asDouble) + else + None + } catch { + case _: NumberFormatException => None + } } } } diff --git a/jvm/src/main/scala/scalajson/ast/JValue.scala b/jvm/src/main/scala/scalajson/ast/JValue.scala index e8956d3..15eed6b 100644 --- a/jvm/src/main/scala/scalajson/ast/JValue.scala +++ b/jvm/src/main/scala/scalajson/ast/JValue.scala @@ -159,17 +159,27 @@ final case class JNumber private[ast] (value: String)( } } - def toBigDecimal: BigDecimal = BigDecimal(value) + def toBigDecimal: Option[BigDecimal] = { + try { + Some(BigDecimal(value)) + } catch { + case _: NumberFormatException => None + } + } def toFloat: Option[Float] = { if ((constructedFlag & NumberFlags.float) == NumberFlags.float) Some(value.toFloat) else { - val asFloat = value.toFloat - if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) - Some(asFloat) - else - None + try { + val asFloat = value.toFloat + if (BigDecimal(value) == BigDecimal(asFloat.toDouble)) + Some(asFloat) + else + None + } catch { + case _: NumberFormatException => None + } } } @@ -177,11 +187,15 @@ final case class JNumber private[ast] (value: String)( if ((constructedFlag & NumberFlags.double) == NumberFlags.double) Some(value.toDouble) else { - val asDouble = value.toDouble - if (BigDecimal(value) == BigDecimal(asDouble)) - Some(asDouble) - else - None + try { + val asDouble = value.toDouble + if (BigDecimal(value) == BigDecimal(asDouble)) + Some(asDouble) + else + None + } catch { + case _: NumberFormatException => None + } } } } diff --git a/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala b/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala index b7667ce..04d436a 100644 --- a/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala +++ b/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala @@ -134,7 +134,13 @@ final case class JNumber(value: String, constructedFlag: Int = 0) } } - def toBigDecimal: BigDecimal = BigDecimal(value) + def toBigDecimal: Option[BigDecimal] = { + try { + Some(BigDecimal(value)) + } catch { + case _: NumberFormatException => None + } + } def toFloat: Option[Float] = { if ((constructedFlag & NumberFlags.float) == NumberFlags.float) From 065fcadc2ec95f3f95e32fce6f3f5efdbc861bef Mon Sep 17 00:00:00 2001 From: Matthew de Detrich Date: Tue, 11 Jul 2017 23:39:13 +0200 Subject: [PATCH 3/5] Fixed equals and hashcode #37 --- js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala | 9 +++++++++ js/src/main/scala/scalajson/ast/unsafe/JValue.scala | 9 +++++++++ .../main/scala-2.10/scalajson.ast/unsafe/JValue.scala | 9 +++++++++ jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala | 9 +++++++++ 4 files changed, 36 insertions(+) diff --git a/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala b/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala index 030a8c9..4e43bc5 100644 --- a/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala +++ b/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala @@ -109,6 +109,15 @@ final case class JNumber(value: String, constructedFlag: Int = 0) case n => n } + override def equals(obj: scala.Any): Boolean = { + obj match { + case jNumber: JNumber => jNumber.value == this.value + case _ => false + } + } + + override def hashCode(): Int = value.## + def toInt: Option[Long] = { if ((constructedFlag & NumberFlags.int) == NumberFlags.int) Some(value.toInt) diff --git a/js/src/main/scala/scalajson/ast/unsafe/JValue.scala b/js/src/main/scala/scalajson/ast/unsafe/JValue.scala index d5308ff..b7f64ec 100644 --- a/js/src/main/scala/scalajson/ast/unsafe/JValue.scala +++ b/js/src/main/scala/scalajson/ast/unsafe/JValue.scala @@ -103,6 +103,15 @@ final case class JNumber(value: String, constructedFlag: Int = 0) case n => n } + override def equals(obj: scala.Any): Boolean = { + obj match { + case jNumber: JNumber => jNumber.value == this.value + case _ => false + } + } + + override def hashCode(): Int = value.## + def toInt: Option[Long] = { if ((constructedFlag & NumberFlags.int) == NumberFlags.int) Some(value.toInt) diff --git a/jvm/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala b/jvm/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala index ab2738d..2582a37 100644 --- a/jvm/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala +++ b/jvm/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala @@ -87,6 +87,15 @@ final case class JNumber(value: String, constructedFlag: Int = 0) case _ => throw new NumberFormatException(value) } + override def equals(obj: scala.Any): Boolean = { + obj match { + case jNumber: JNumber => jNumber.value == this.value + case _ => false + } + } + + override def hashCode(): Int = value.## + def toInt: Option[Long] = { if ((constructedFlag & NumberFlags.int) == NumberFlags.int) Some(value.toInt) diff --git a/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala b/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala index 04d436a..a6f0229 100644 --- a/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala +++ b/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala @@ -90,6 +90,15 @@ final case class JNumber(value: String, constructedFlag: Int = 0) case _ => throw new NumberFormatException(value) } + override def equals(obj: scala.Any): Boolean = { + obj match { + case jNumber: JNumber => jNumber.value == this.value + case _ => false + } + } + + override def hashCode(): Int = value.## + def toInt: Option[Long] = { if ((constructedFlag & NumberFlags.int) == NumberFlags.int) Some(value.toInt) From 403d9b3e4fd4c478840554a88233855e38590050 Mon Sep 17 00:00:00 2001 From: Matthew de Detrich Date: Tue, 11 Jul 2017 23:42:49 +0200 Subject: [PATCH 4/5] Removed unused import --- js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala b/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala index 4e43bc5..9424972 100644 --- a/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala +++ b/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala @@ -4,7 +4,6 @@ package unsafe import scalajson.ast import scalajson.ast._ import scala.scalajs.js -import scala.scalajs.js.annotation.JSExport /** Represents a JSON Value which may be invalid. Internally uses mutable * collections when its desirable to do so, for performance and other reasons From 171f30d992db344194fbcc8c731a6bbd86cc34f5 Mon Sep 17 00:00:00 2001 From: Matthew de Detrich Date: Fri, 10 Nov 2017 19:09:23 +0100 Subject: [PATCH 5/5] scalafmt --- .../scala/scalajson/ast/unsafe/JValue.scala | 26 +++++++++---------- .../scala/scalajson/ast/unsafe/JValue.scala | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/js/src/main/scala/scalajson/ast/unsafe/JValue.scala b/js/src/main/scala/scalajson/ast/unsafe/JValue.scala index 87341d6..3ac031d 100644 --- a/js/src/main/scala/scalajson/ast/unsafe/JValue.scala +++ b/js/src/main/scala/scalajson/ast/unsafe/JValue.scala @@ -106,7 +106,7 @@ final case class JNumber(value: String, constructedFlag: Int = 0) override def equals(obj: scala.Any): Boolean = { obj match { case jNumber: JNumber => jNumber.value == this.value - case _ => false + case _ => false } } @@ -326,12 +326,12 @@ final case class JObject(value: js.Array[JField] = js.Array()) extends JValue { else { result = 31 * result + elem.field.## elem.value match { - case unsafe.JNull => unsafe.JNull.## - case unsafe.JString(s) => s.## - case unsafe.JBoolean(b) => b.## - case unsafe.JNumber(i, _) => i.## - case unsafe.JArray(a) => a.## - case unsafe.JObject(obj) => obj.## + case unsafe.JNull => unsafe.JNull.## + case unsafe.JString(s) => s.## + case unsafe.JBoolean(b) => b.## + case unsafe.JNumber(i, _) => i.## + case unsafe.JArray(a) => a.## + case unsafe.JObject(obj) => obj.## } }) index += 1 @@ -398,12 +398,12 @@ final case class JArray(value: js.Array[JValue] = js.Array()) extends JValue { result = 31 * result + (if (elem == null) 0 else { elem match { - case unsafe.JNull => unsafe.JNull.## - case unsafe.JString(s) => s.## - case unsafe.JBoolean(b) => b.## - case unsafe.JNumber(i, _) => i.## - case unsafe.JArray(a) => a.## - case unsafe.JObject(obj) => obj.## + case unsafe.JNull => unsafe.JNull.## + case unsafe.JString(s) => s.## + case unsafe.JBoolean(b) => b.## + case unsafe.JNumber(i, _) => i.## + case unsafe.JArray(a) => a.## + case unsafe.JObject(obj) => obj.## } }) index += 1 diff --git a/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala b/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala index fad529a..93d7b1f 100644 --- a/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala +++ b/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala @@ -93,7 +93,7 @@ final case class JNumber(value: String, constructedFlag: Int = 0) override def equals(obj: scala.Any): Boolean = { obj match { case jNumber: JNumber => jNumber.value == this.value - case _ => false + case _ => false } }