Skip to content

Commit 7db4283

Browse files
committed
Added in various JNumber conversions #37
There is now a constructedFlag which tracks how a JNumber is created
1 parent 99ee084 commit 7db4283

File tree

13 files changed

+841
-105
lines changed

13 files changed

+841
-105
lines changed

benchmark/jvm/src/test/scala/scalajson/ast/PrivateBenchmark.scala renamed to benchmark/jvm/src/test/scala/scalajson/ast/EqualsHashcodeBenchmark.scala

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ package scalajson.ast
33
import benchmark.Generators
44
import org.scalameter.Bench
55

6-
/**
7-
* Created by matthewdedetrich on 17/10/16.
8-
*/
9-
object PrivateBenchmark extends Bench.ForkedTime {
6+
object EqualsHashcodeBenchmark extends Bench.ForkedTime {
107

118
performance of "privateMethods" in {
129
measure method "hashcode" in {
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package scalajson.ast
2+
3+
import org.scalameter.{Bench, Gen}
4+
5+
object JNumberConversionBenchmark extends Bench.ForkedTime {
6+
def intString: Gen[String] =
7+
for {
8+
size <- Gen.range("seed")(300000, 1500000, 300000)
9+
} yield {
10+
size.toString
11+
}
12+
13+
def floatString: Gen[String] =
14+
for {
15+
a <- Gen.range("seed")(300000, 1500000, 300000)
16+
b <- Gen.range("seed")(300000, 1500000, 300000)
17+
} yield {
18+
s"$a.$b".toFloat.toString
19+
}
20+
21+
private val intConstructedFlag = NumberFlags.intConstructed
22+
private val floatConstructedFlag = NumberFlags.floatConstructed
23+
24+
performance of "intBitFlagCheckSuccess" in {
25+
using(intString) in { value: String =>
26+
if ((intConstructedFlag & NumberFlags.int) == NumberFlags.int)
27+
Some(value.toInt)
28+
else {
29+
try {
30+
val asInt = value.toInt
31+
if (BigInt(value) == BigInt(asInt))
32+
Some(asInt)
33+
else
34+
None
35+
} catch {
36+
case _: NumberFormatException => None
37+
}
38+
}
39+
}
40+
}
41+
42+
performance of "intManualCheckSuccess" in {
43+
using(intString) in { value: String =>
44+
try {
45+
val asInt = value.toInt
46+
if (BigInt(value) == BigInt(asInt))
47+
Some(asInt)
48+
else
49+
None
50+
} catch {
51+
case _: NumberFormatException => None
52+
}
53+
}
54+
}
55+
56+
performance of "floatBitFlagCheckSuccess" in {
57+
using(floatString) in { value: String =>
58+
if ((floatConstructedFlag & NumberFlags.float) == NumberFlags.float)
59+
Some(value.toFloat)
60+
else {
61+
val asFloat = value.toFloat
62+
if (BigDecimal(value) == BigDecimal(asFloat.toDouble))
63+
Some(asFloat)
64+
else
65+
None
66+
}
67+
}
68+
}
69+
70+
performance of "floatManualCheckSuccess" in {
71+
using(floatString) in { value: String =>
72+
val asFloat = value.toFloat
73+
if (BigDecimal(value) == BigDecimal(asFloat.toDouble))
74+
Some(asFloat)
75+
else
76+
None
77+
}
78+
}
79+
}

js/src/main/scala-2.10/scalajson.ast/JValue.scala

Lines changed: 87 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,20 @@ final case class JString(value: String) extends JValue {
5050
}
5151

5252
object JNumber {
53-
def apply(value: Int): JNumber = new JNumber(value.toString)
53+
def apply(value: Int): JNumber =
54+
new JNumber(value.toString)(NumberFlags.intConstructed)
5455

55-
def apply(value: Integer): JNumber = new JNumber(value.toString)
56+
def apply(value: Integer): JNumber =
57+
new JNumber(value.toString)(NumberFlags.intConstructed)
5658

57-
def apply(value: Long): JNumber = new JNumber(value.toString)
59+
def apply(value: Long): JNumber =
60+
new JNumber(value.toString)(NumberFlags.longConstructed)
5861

59-
def apply(value: BigInt): JNumber = new JNumber(value.toString())
62+
def apply(value: BigInt): JNumber =
63+
new JNumber(value.toString())(NumberFlags.bigIntConstructed)
6064

61-
def apply(value: BigDecimal): JNumber = new JNumber(value.toString())
65+
def apply(value: BigDecimal): JNumber =
66+
new JNumber(value.toString())(NumberFlags.bigDecimalConstructed)
6267

6368
/**
6469
* @param value
@@ -67,7 +72,7 @@ object JNumber {
6772
def apply(value: Double): JValue = value match {
6873
case n if n.isNaN => JNull
6974
case n if n.isInfinity => JNull
70-
case _ => new JNumber(value.toString)
75+
case _ => new JNumber(value.toString)(NumberFlags.doubleConstructed)
7176
}
7277

7378
/**
@@ -77,12 +82,12 @@ object JNumber {
7782
def apply(value: Float): JValue = value match {
7883
case n if java.lang.Float.isNaN(n) => JNull
7984
case n if n.isInfinity => JNull
80-
case _ => new JNumber(value.toString)
85+
case _ => new JNumber(value.toString)(NumberFlags.floatConstructed)
8186
}
8287

8388
def fromString(value: String): Option[JNumber] =
8489
value match {
85-
case jNumberRegex(_ *) => Some(new JNumber(value))
90+
case jNumberRegex(_ *) => Some(new JNumber(value)(0))
8691
case _ => None
8792
}
8893

@@ -97,15 +102,9 @@ object JNumber {
97102
*/
98103
// Due to a restriction in Scala 2.10, we cant override/replace the default apply method
99104
// generated by the compiler even when the constructor itself is marked private
100-
final class JNumber private[ast] (val value: String) extends JValue {
101-
102-
/**
103-
* Javascript specification for numbers specify a [[scala.Double]], so this is the default export method to `Javascript`
104-
*
105-
* @param value
106-
*/
107-
def this(value: Double) = this(value.toString)
108-
105+
final class JNumber private[ast] (val value: String)(
106+
private[ast] val constructedFlag: Int)
107+
extends JValue {
109108
override def toUnsafe: unsafe.JValue = unsafe.JNumber(value)
110109

111110
override def toJsAny: js.Any = value.toDouble match {
@@ -143,9 +142,79 @@ final class JNumber private[ast] (val value: String) extends JValue {
143142

144143
def copy(value: String): JNumber =
145144
value match {
146-
case jNumberRegex(_ *) => new JNumber(value)
145+
case jNumberRegex(_ *) => new JNumber(value)(0)
147146
case _ => throw new NumberFormatException(value)
148147
}
148+
149+
def toInt: Option[Long] = {
150+
if ((constructedFlag & NumberFlags.int) == NumberFlags.int)
151+
Some(value.toInt)
152+
else {
153+
try {
154+
val asInt = value.toInt
155+
if (BigInt(value) == BigInt(asInt))
156+
Some(asInt)
157+
else
158+
None
159+
} catch {
160+
case _: NumberFormatException => None
161+
}
162+
}
163+
}
164+
165+
def toLong: Option[Long] = {
166+
if ((constructedFlag & NumberFlags.long) == NumberFlags.long)
167+
Some(value.toLong)
168+
else {
169+
try {
170+
val asLong = value.toLong
171+
if (BigInt(value) == BigInt(asLong))
172+
Some(asLong)
173+
else
174+
None
175+
} catch {
176+
case _: NumberFormatException => None
177+
}
178+
}
179+
}
180+
181+
def toBigInt: Option[BigInt] = {
182+
if ((constructedFlag & NumberFlags.bigInt) == NumberFlags.bigInt)
183+
Some(BigInt(value))
184+
else {
185+
try {
186+
Some(BigInt(value))
187+
} catch {
188+
case _: NumberFormatException => None
189+
}
190+
}
191+
}
192+
193+
def toBigDecimal: BigDecimal = BigDecimal(value)
194+
195+
def toFloat: Option[Float] = {
196+
if ((constructedFlag & NumberFlags.float) == NumberFlags.float)
197+
Some(value.toFloat)
198+
else {
199+
val asFloat = value.toFloat
200+
if (BigDecimal(value) == BigDecimal(asFloat.toDouble))
201+
Some(asFloat)
202+
else
203+
None
204+
}
205+
}
206+
207+
def toDouble: Option[Double] = {
208+
if ((constructedFlag & NumberFlags.double) == NumberFlags.double)
209+
Some(value.toDouble)
210+
else {
211+
val asDouble = value.toDouble
212+
if (BigDecimal(value) == BigDecimal(asDouble))
213+
Some(asDouble)
214+
else
215+
None
216+
}
217+
}
149218
}
150219

151220
/** Represents a JSON Boolean value, which can either be a

js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala

Lines changed: 90 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,28 @@ final case class JString(value: String) extends JValue {
5656
}
5757

5858
object JNumber {
59-
def apply(value: Int): JNumber = JNumber(value.toString)
59+
def apply(value: Int): JNumber =
60+
JNumber(value.toString, NumberFlags.intConstructed)
6061

61-
def apply(value: Long): JNumber = JNumber(value.toString)
62+
def apply(value: Long): JNumber =
63+
JNumber(value.toString, NumberFlags.longConstructed)
6264

63-
def apply(value: BigInt): JNumber = JNumber(value.toString)
65+
def apply(value: BigInt): JNumber =
66+
JNumber(value.toString, NumberFlags.bigIntConstructed)
6467

65-
def apply(value: BigDecimal): JNumber = JNumber(value.toString)
68+
def apply(value: BigDecimal): JNumber =
69+
JNumber(value.toString, NumberFlags.bigDecimalConstructed)
6670

67-
def apply(value: Float): JNumber = JNumber(value.toString)
71+
def apply(value: Float): JNumber =
72+
JNumber(value.toString, NumberFlags.floatConstructed)
6873

69-
def apply(value: Double): JNumber = JNumber(value.toString)
74+
def apply(value: Double): JNumber =
75+
JNumber(value.toString, NumberFlags.doubleConstructed)
7076

71-
def apply(value: Integer): JNumber = JNumber(value.toString)
77+
def apply(value: Integer): JNumber =
78+
JNumber(value.toString, NumberFlags.intConstructed)
7279

73-
def apply(value: Array[Char]): JNumber = JNumber(new String(value))
80+
def apply(value: Array[Char]): JNumber = JNumber(new String(value), (0))
7481
}
7582

7683
/** Represents a JSON number value.
@@ -84,10 +91,11 @@ object JNumber {
8491
* @author Matthew de Detrich
8592
*/
8693
// JNumber is internally represented as a string, to improve performance
87-
final case class JNumber(value: String) extends JValue {
94+
final case class JNumber(value: String, constructedFlag: Int = 0)
95+
extends JValue {
8896
override def toStandard: ast.JValue =
8997
value match {
90-
case jNumberRegex(_ *) => new ast.JNumber(value)
98+
case jNumberRegex(_ *) => new ast.JNumber(value)(constructedFlag)
9199
case _ => throw new NumberFormatException(value)
92100
}
93101

@@ -100,6 +108,76 @@ final case class JNumber(value: String) extends JValue {
100108
case n if n.isInfinity => null
101109
case n => n
102110
}
111+
112+
def toInt: Option[Long] = {
113+
if ((constructedFlag & NumberFlags.int) == NumberFlags.int)
114+
Some(value.toInt)
115+
else {
116+
try {
117+
val asInt = value.toInt
118+
if (BigInt(value) == BigInt(asInt))
119+
Some(asInt)
120+
else
121+
None
122+
} catch {
123+
case _: NumberFormatException => None
124+
}
125+
}
126+
}
127+
128+
def toLong: Option[Long] = {
129+
if ((constructedFlag & NumberFlags.long) == NumberFlags.long)
130+
Some(value.toLong)
131+
else {
132+
try {
133+
val asLong = value.toLong
134+
if (BigInt(value) == BigInt(asLong))
135+
Some(asLong)
136+
else
137+
None
138+
} catch {
139+
case _: NumberFormatException => None
140+
}
141+
}
142+
}
143+
144+
def toBigInt: Option[BigInt] = {
145+
if ((constructedFlag & NumberFlags.bigInt) == NumberFlags.bigInt)
146+
Some(BigInt(value))
147+
else {
148+
try {
149+
Some(BigInt(value))
150+
} catch {
151+
case _: NumberFormatException => None
152+
}
153+
}
154+
}
155+
156+
def toBigDecimal: BigDecimal = BigDecimal(value)
157+
158+
def toFloat: Option[Float] = {
159+
if ((constructedFlag & NumberFlags.float) == NumberFlags.float)
160+
Some(value.toFloat)
161+
else {
162+
val asFloat = value.toFloat
163+
if (BigDecimal(value) == BigDecimal(asFloat.toDouble))
164+
Some(asFloat)
165+
else
166+
None
167+
}
168+
}
169+
170+
def toDouble: Option[Double] = {
171+
if ((constructedFlag & NumberFlags.double) == NumberFlags.double)
172+
Some(value.toDouble)
173+
else {
174+
val asDouble = value.toDouble
175+
if (BigDecimal(value) == BigDecimal(asDouble))
176+
Some(asDouble)
177+
else
178+
None
179+
}
180+
}
103181
}
104182

105183
/** 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 {
232310
case unsafe.JNull => unsafe.JNull.##
233311
case unsafe.JString(s) => s.##
234312
case unsafe.JBoolean(b) => b.##
235-
case unsafe.JNumber(i) => i.##
313+
case unsafe.JNumber(i, _) => i.##
236314
case unsafe.JArray(a) => a.##
237315
case unsafe.JObject(obj) => obj.##
238316
}
@@ -304,7 +382,7 @@ final case class JArray(value: js.Array[JValue] = js.Array()) extends JValue {
304382
case unsafe.JNull => unsafe.JNull.##
305383
case unsafe.JString(s) => s.##
306384
case unsafe.JBoolean(b) => b.##
307-
case unsafe.JNumber(i) => i.##
385+
case unsafe.JNumber(i, _) => i.##
308386
case unsafe.JArray(a) => a.##
309387
case unsafe.JObject(obj) => obj.##
310388
}

0 commit comments

Comments
 (0)