Skip to content

Commit

Permalink
#37 Changed the JNumber conversion methods to follow semantics of types
Browse files Browse the repository at this point in the history
  • Loading branch information
mdedetrich committed Dec 4, 2017
1 parent 302f1dc commit f7539ad
Show file tree
Hide file tree
Showing 11 changed files with 77 additions and 50 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,28 @@ in an undefined manner.
Do note that according to the JSON spec, whether to order keys for a `JObject` is not specified. Also note that `Map`
disregards ordering for equality, however `Array`/`js.Array` equality takes ordering into account.

## Number conversions
ScalaJSON `JNumber` provides conversions to various number types with the following conventions

* `toInt`: Safe conversion to `Int` which accounts for values such as `1.0` and `100.00e-2` (which both evaluate to `1`).
Also safely detects over/underflow.
* `toLong`: Safe conversion to `Long` which accounts for values such as `1.0` and `100.00e-2` (which both evaluate to `1`).
Also safely detects over/underflow.
* `toDouble`: Converts to a `Double` assuming the same semantics of `Double` (i.e. precision loss is expected).
* `toFloat`: Converts to a `Float` assuming the same semantics of `Float` (i.e. precision loss is expected).
* `toBigInt`: Converts to a `BigInt` which accounts for values such as `1.0` and `100.00e-2` (which evaluates to `1`).
Can construct a `BigInt`for as much as memory as the system has (if your system runs out of memory this is considered
undefined behaviour).
* `toBigDecimal`: Converts to a `BigDecimal` with all of the caveats of `BigDecimal` construction. The `BigDecimal` is
constructed with `MathContext.UNLIMITED` precision.

With the `.toFloat` and `.toDouble` methods, if you don't want any loss in precision, its advisable to convert to
`BigDecimal` first and then work from there, i.e. when working with `Decimal`/`Float`, its implied that you will
have loss of precision.

Remember that in all cases if these methods are not applicable, you can always use the `.value` field to get the
original string representation of the number.

## Scala.js
ScalaJSON also provides support for [Scala.js](https://github.com/scala-js/scala-js).
The usage of Scala.js mirrors the usage of Scala on the JVM however Scala.js also implements
Expand All @@ -103,6 +125,10 @@ underlying number (numbers in Javascript are represented as double precision flo
You can use the `.value` method on a `scalajson.ast.JNumber`/`scalajson.ast.unsafe.JNumber` to
get the raw string value as a solution to this problem.

Further, `toFloat` on `JNumber` (see [Number Conversions](#number-conversions) ) can have different semantics on Scala.js, depending on whether you have
strict-floats enabled in your application. Please see the [Scala.js semantics page](https://www.scala-js.org/doc/semantics.html)
for more information.

## jNumberRegex
`scalajson.JNumber` uses `jNumberRegex` to validate whether a number is a valid
JSON number. One can use `jNumberRegex` explicitly if you want to use the validation that
Expand Down
4 changes: 3 additions & 1 deletion js/src/main/scala-2.10/scalajson.ast/JValue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ final class JNumber private[ast] (val value: String) extends JValue {

def toLong: Option[Long] = scalajson.ast.toLong(value)

def toDouble: Option[Double] = scalajson.ast.toDouble(value)
def toDouble: Double = scalajson.ast.toDouble(value)

def toFloat: Float = scalajson.ast.toFloat(value)

def toBigDecimal: Option[BigDecimal] = scalajson.ast.toBigDecimal(value)
}
Expand Down
32 changes: 17 additions & 15 deletions js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,14 @@ object JNumber {
final case class JNumber(value: String) extends JValue {
override def toStandard: ast.JValue =
value match {
case jNumberRegex(_ *) => new ast.JNumber(value)
case _ => throw new NumberFormatException(value)
case jNumberRegex(_*) => new ast.JNumber(value)
case _ => throw new NumberFormatException(value)
}

override def toJsAny: js.Any = value.toDouble match {
case n if n.isNaN => null
case n if n.isNaN => null
case n if n.isInfinity => null
case n => n
case n => n
}

def toInt: Option[Int] = scalajson.ast.toInt(value)
Expand All @@ -103,7 +103,9 @@ final case class JNumber(value: String) extends JValue {

def toLong: Option[Long] = scalajson.ast.toLong(value)

def toDouble: Option[Double] = scalajson.ast.toDouble(value)
def toDouble: Double = scalajson.ast.toDouble(value)

def toFloat: Float = scalajson.ast.toFloat(value)

def toBigDecimal: Option[BigDecimal] = scalajson.ast.toBigDecimal(value)
}
Expand Down Expand Up @@ -235,11 +237,11 @@ 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.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.##
}
})
Expand Down Expand Up @@ -307,11 +309,11 @@ 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.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.##
}
})
Expand Down
4 changes: 3 additions & 1 deletion js/src/main/scala/scalajson/ast/JValue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ final case class JNumber private[ast] (value: String) extends JValue {

def toLong: Option[Long] = scalajson.ast.toLong(value)

def toDouble: Option[Double] = scalajson.ast.toDouble(value)
def toDouble: Double = scalajson.ast.toDouble(value)

def toFloat: Float = scalajson.ast.toFloat(value)

def toBigDecimal: Option[BigDecimal] = scalajson.ast.toBigDecimal(value)
}
Expand Down
4 changes: 3 additions & 1 deletion js/src/main/scala/scalajson/ast/unsafe/JValue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ final case class JNumber(value: String) extends JValue {

def toLong: Option[Long] = scalajson.ast.toLong(value)

def toDouble: Option[Double] = scalajson.ast.toDouble(value)
def toDouble: Double = scalajson.ast.toDouble(value)

def toFloat: Float = scalajson.ast.toFloat(value)

def toBigDecimal: Option[BigDecimal] = scalajson.ast.toBigDecimal(value)
}
Expand Down
4 changes: 3 additions & 1 deletion jvm/src/main/scala-2.10/scalajson.ast/JValue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ final class JNumber private[ast] (val value: String) extends JValue {

def toLong: Option[Long] = scalajson.ast.toLong(value)

def toDouble: Option[Double] = scalajson.ast.toDouble(value)
def toDouble: Double = scalajson.ast.toDouble(value)

def toFloat: Float = scalajson.ast.toFloat(value)

def toBigDecimal: Option[BigDecimal] = scalajson.ast.toBigDecimal(value)
}
Expand Down
4 changes: 3 additions & 1 deletion jvm/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ final case class JNumber(value: String) extends JValue {

def toLong: Option[Long] = scalajson.ast.toLong(value)

def toDouble: Option[Double] = scalajson.ast.toDouble(value)
def toDouble: Double = scalajson.ast.toDouble(value)

def toFloat: Double = scalajson.ast.toFloat(value)

def toBigDecimal: Option[BigDecimal] = scalajson.ast.toBigDecimal(value)
}
Expand Down
4 changes: 3 additions & 1 deletion jvm/src/main/scala/scalajson/ast/JValue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ final case class JNumber private[ast] (value: String) extends JValue {

def toLong: Option[Long] = scalajson.ast.toLong(value)

def toDouble: Option[Double] = scalajson.ast.toDouble(value)
def toDouble: Double = scalajson.ast.toDouble(value)

def toFloat: Float = scalajson.ast.toFloat(value)

def toBigDecimal: Option[BigDecimal] = scalajson.ast.toBigDecimal(value)
}
Expand Down
4 changes: 3 additions & 1 deletion jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ final case class JNumber(value: String) extends JValue {

def toLong: Option[Long] = scalajson.ast.toLong(value)

def toDouble: Option[Double] = scalajson.ast.toDouble(value)
def toDouble: Double = scalajson.ast.toDouble(value)

def toFloat: Float = scalajson.ast.toFloat(value)

def toBigDecimal: Option[BigDecimal] = scalajson.ast.toBigDecimal(value)
}
Expand Down
21 changes: 7 additions & 14 deletions shared/src/main/scala-2.10/scalajson.ast/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -698,25 +698,18 @@ package object ast {
}
}

private[ast] def toDouble(value: String): Option[Double] = {
try {
val asDouble = value.toDouble
if (BigDecimal(value, MathContext.UNLIMITED) == BigDecimal(
asDouble,
MathContext.UNLIMITED))
Some(asDouble)
else
None
} catch {
case _: java.lang.NumberFormatException => None
}
}
@inline private[ast] def toDouble(value: String): Double =
value.toDouble

@inline private[ast] def toFloat(value: String): Float =
value.toFloat

private[ast] def toBigDecimal(value: String): Option[BigDecimal] = {
try {
Some(BigDecimal(value, MathContext.UNLIMITED))
} catch {
case _: java.lang.NumberFormatException => None
case _: NumberFormatException | _: ArithmeticException =>
None
}
}
}
20 changes: 6 additions & 14 deletions shared/src/main/scala/scalajson/ast/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -698,25 +698,17 @@ package object ast {
}
}

private[ast] def toDouble(value: String): Option[Double] = {
try {
val asDouble = value.toDouble
if (BigDecimal(value, MathContext.UNLIMITED) == BigDecimal(
asDouble,
MathContext.UNLIMITED))
Some(asDouble)
else
None
} catch {
case _: java.lang.NumberFormatException => None
}
}
@inline private[ast] def toDouble(value: String): Double =
value.toDouble

@inline private[ast] def toFloat(value: String): Float =
value.toFloat

private[ast] def toBigDecimal(value: String): Option[BigDecimal] = {
try {
Some(BigDecimal(value, MathContext.UNLIMITED))
} catch {
case _: java.lang.NumberFormatException => None
case _: NumberFormatException | _: ArithmeticException => None
}
}
}

0 comments on commit f7539ad

Please sign in to comment.