-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FIX parsing for Long in scientific notation #674
base: main
Are you sure you want to change the base?
Changes from all commits
7b60483
6eb482e
978bbfc
f8c8a70
27ea0c3
d2dd387
bbebe50
f56d052
b59ab4f
5c54741
8350e79
6870485
70ea50c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ package ast | |
import java.lang.Double.{isInfinite, isNaN} | ||
import scala.collection.mutable | ||
import scala.reflect.ClassTag | ||
import scala.util.Try | ||
import scala.util.hashing.MurmurHash3 | ||
|
||
class WrongValueException(e: String, g: String) extends Exception(s"expected $e, got $g") | ||
|
@@ -223,28 +224,30 @@ case class DoubleNum(n: Double) extends JNum { | |
|
||
case class DeferLong(s: String) extends JNum { | ||
|
||
lazy val n: Long = util.parseLongUnsafe(s) | ||
lazy val nOpt: Option[Long] = Try(util.parseLong(s)).toOption | ||
lazy val n: Long = nOpt.getOrElse(throw new InvalidNumException(s)) | ||
|
||
final override def getInt: Option[Int] = Some(n.toInt) | ||
final override def getLong: Option[Long] = Some(n) | ||
final override def getDouble: Option[Double] = Some(n.toDouble) | ||
final override def getInt: Option[Int] = nOpt.map(_.toInt) | ||
final override def getLong: Option[Long] = nOpt | ||
final override def getDouble: Option[Double] = nOpt.map(_.toDouble) | ||
final override def getBigInt: Option[BigInt] = Some(BigInt(s)) | ||
final override def getBigDecimal: Option[BigDecimal] = Some(BigDecimal(s)) | ||
|
||
final override def asInt: Int = n.toInt | ||
final override def asLong: Long = n | ||
final override def asDouble: Double = n.toDouble | ||
final override def asInt: Int = nOpt.map(_.toInt).getOrElse(throw new InvalidNumException(s)) | ||
final override def asLong: Long = nOpt.getOrElse(throw new InvalidNumException(s)) | ||
final override def asDouble: Double = nOpt.map(_.toDouble).getOrElse(throw new InvalidNumException(s)) | ||
final override def asBigInt: BigInt = BigInt(s) | ||
final override def asBigDecimal: BigDecimal = BigDecimal(s) | ||
|
||
final override def hashCode: Int = n.## | ||
final override def hashCode: Int = if (nOpt.isEmpty) s.## else nOpt.get.## | ||
|
||
final override def equals(that: Any): Boolean = | ||
that match { | ||
case LongNum(n2) => n == n2 | ||
case DoubleNum(n2) => JNum.hybridEq(n, n2) | ||
case jn: DeferLong => n == jn.asLong | ||
case jn: DeferNum => JNum.hybridEq(n, jn.asDouble) | ||
(nOpt, that) match { | ||
case (None, _) => false | ||
case (Some(n), LongNum(n2)) => n == n2 | ||
case (Some(n), DoubleNum(n2)) => JNum.hybridEq(n, n2) | ||
case (Some(n), jn: DeferLong) => n == jn.asLong | ||
case (Some(n), jn: DeferNum) => JNum.hybridEq(n, jn.asDouble) | ||
case _ => false | ||
} | ||
} | ||
|
@@ -254,13 +257,13 @@ case class DeferNum(s: String) extends JNum { | |
lazy val n: Double = java.lang.Double.parseDouble(s) | ||
|
||
final override def getInt: Option[Int] = Some(n.toInt) | ||
final override def getLong: Option[Long] = Some(util.parseLongUnsafe(s)) | ||
final override def getLong: Option[Long] = Some(n.toLong) | ||
final override def getDouble: Option[Double] = Some(n) | ||
final override def getBigInt: Option[BigInt] = Some(BigDecimal(s).toBigInt) | ||
final override def getBigDecimal: Option[BigDecimal] = Some(BigDecimal(s)) | ||
|
||
final override def asInt: Int = n.toInt | ||
final override def asLong: Long = util.parseLongUnsafe(s) | ||
final override def asLong: Long = n.toLong | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checking my understanding: right now it sometimes returns an incorrect value, and you want it to throw? Changing behavior to start throwing scares me, particularly in a method not documented to throw. But silently returning bad values isn't great, either. And I guess it would align the behavior more closely with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, I would like it not to throw but to produce the correct result. Looking at both
So these functions cannot handle
// from JawnFacade
final def jnum(s: CharSequence, decIndex: Int, expIndex: Int): JValue =
if (decIndex == -1 && expIndex == -1)
DeferLong(s.toString)
else
DeferNum(s.toString) So basically we would fall in (non-)solution 3 Edit: I initially thought it was just scientific notation and adding a flag could solve the problem, but looking again at the code I realized even decimal point is not handled by |
||
final override def asDouble: Double = n | ||
final override def asBigInt: BigInt = BigDecimal(s).toBigInt | ||
final override def asBigDecimal: BigDecimal = BigDecimal(s) | ||
|
@@ -271,7 +274,9 @@ case class DeferNum(s: String) extends JNum { | |
that match { | ||
case LongNum(n2) => JNum.hybridEq(n2, n) | ||
case DoubleNum(n2) => n == n2 | ||
case jn: DeferLong => JNum.hybridEq(jn.asLong, n) | ||
case jn: DeferLong => | ||
try JNum.hybridEq(jn.asLong, n) | ||
catch { case _: InvalidNumException => false } | ||
case jn: DeferNum => n == jn.asDouble | ||
case _ => false | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's strange that both of these methods return
Some
with theJNum
is not integral, but I guess that's probably behavior best left alone at this point.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understood: are you suggesting to leave the code as it is? I made a small demo program and as it is it produces wrong results, unless I'm missing something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PS digging down the rabbit hole it seems that also the use of
parseLongUnsafe
inDeferLong
is not correct, as there is no check at parser level that the digit string parsed represent a number in the Long range, just that there is no.
ore
.After the string is passed to jnum in JawnFacade (see code in comment above)
And in DeferLong
So this too can produce wrong results for number outside of Long range (which are anyway valid as JSON as everything is supposed to be encoded as double). This also hint at the matter of who is in charge of semantic checks: IMHO should be the Facade, but comment on parseLongUnsafe seems to suggest the contrary:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried to fix this too, but somehow we lose idempotency on JS CharSequence/String parsing. I was unable to investigate how this happens, but it's just JS. Would it be a good idea to rewire CharSequence to String parsing in JS implementation? At the end of the day we have not a CharSequence type in JS.