Skip to content
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 long numbers problem #98

Merged
merged 2 commits into from
Jun 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions core/src/main/scala/cats/xml/Xml.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,27 +64,32 @@ object Xml {
def ofString(value: String): XmlString = XmlString(value)
def ofChar(value: Char): XmlChar = XmlChar(value)
def ofBoolean(value: Boolean): XmlBool = XmlBool(value)
def ofByte(value: Byte): XmlNumber = XmlLong(value.toLong)
def ofShort(value: Short): XmlNumber = XmlLong(value.toLong)
def ofInt(value: Int): XmlNumber = XmlLong(value.toLong)
def ofByte(value: Byte): XmlNumber = XmlByte(value)
def ofShort(value: Short): XmlNumber = XmlShort(value)
def ofInt(value: Int): XmlNumber = XmlInt(value)
def ofLong(value: Long): XmlNumber = XmlLong(value)
def ofFloat(value: Float): XmlNumber = XmlFloat(value)
def ofDouble(value: Double): XmlNumber = XmlDouble(value)
def ofBigInt(value: BigInt): XmlNumber = XmlBigDecimal(BigDecimal(value))
def ofBigInt(value: BigInt): XmlNumber = XmlBigInt(value)
def ofBigDecimal(value: BigDecimal): XmlNumber = XmlBigDecimal(value)
def ofArray[T <: XmlData](value: Array[T]): XmlArray[T] = XmlArray(value)
def ofSeq[T <: XmlData: ClassTag](value: Seq[T]): XmlArray[T] = XmlArray(value.toArray)
def ofValues[T <: XmlData: ClassTag](value: T*): XmlArray[T] = XmlArray(value.toArray)
def fromNumberString(str: String): Option[XmlNumber] =
def fromNumberString(str: String): Option[XmlNumber] = {
Try(BigDecimal.exact(str)).toOption.map {
case db if db.isValidByte => ofLong(db.toByteExact.toLong)
case db if db.isValidShort => ofLong(db.toShortExact.toLong)
case db if db.isValidInt => ofLong(db.toIntExact.toLong)
case db if db.isValidByte => ofByte(db.toByteExact)

Check notice

Code scanning / Codacy-scalameta-pro (reported by Codacy)

Prohibits same expression on cases inside the same scope

Duplicated case statements
case db if db.isValidShort => ofShort(db.toShortExact)
case db if db.isValidInt => ofInt(db.toIntExact)
case bd if bd.isValidLong => ofLong(bd.toLongExact)
case db if db.isDecimalFloat => ofFloat(db.floatValue)
case db if db.isDecimalDouble => ofDouble(db.doubleValue)
case bd => ofBigDecimal(bd)
case bd =>

Check notice

Code scanning / Codacy-scalameta-pro (reported by Codacy)

Prohibit case statement pattern match from being lowercase.

Lower case pattern matching.
bd.toBigIntExact match {
case Some(bi) => ofBigInt(bi)
case None => ofBigDecimal(bd)
}
}
}

def fromDataString(value: String): XmlData = {
fromNumberString(value)
Expand Down
35 changes: 33 additions & 2 deletions core/src/main/scala/cats/xml/xmlData.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,21 @@ object XmlData {
sealed trait XmlNumber extends XmlData with Serializable {

final def toBigDecimal: Option[BigDecimal] = this match {
case XmlByte(value) => Some(BigDecimal(value.toInt))
case XmlShort(value) => Some(BigDecimal(value.toInt))
case XmlInt(value) => Some(BigDecimal(value))
case XmlLong(value) => Some(BigDecimal(value))
case XmlFloat(value) => Some(new JavaBigDecimal(java.lang.Float.toString(value)))
case XmlDouble(value) => Some(JavaBigDecimal.valueOf(value))
case XmlBigInt(value) => Some(BigDecimal(value))
case XmlBigDecimal(value) => Some(value)
}

final def toBigInt: Option[BigInt] = this match {
case XmlLong(value) => Some(BigInt(value))
case XmlByte(value) => Some(BigInt(value.toInt))
case XmlShort(value) => Some(BigInt(value.toInt))
case XmlInt(value) => Some(BigInt(value))
case XmlLong(value) => Some(BigInt(value))
case XmlFloat(value) =>
Option(new JavaBigDecimal(java.lang.Float.toString(value)))
.filter(XmlNumber.bigDecimalIsWhole)
Expand All @@ -55,20 +62,29 @@ object XmlData {
Option(JavaBigDecimal.valueOf(value))
.filter(XmlNumber.bigDecimalIsWhole)
.map(bd => new BigInt(bd.toBigInteger))
case XmlBigInt(value) => Some(value)
case XmlBigDecimal(value) => value.toBigIntExact
}

final def toDouble: Double = this match {
case XmlByte(value) => value.toDouble
case XmlShort(value) => value.toDouble
case XmlInt(value) => value.toDouble
case XmlLong(value) => value.toDouble
case XmlFloat(value) => new JavaBigDecimal(java.lang.Float.toString(value)).doubleValue
case XmlDouble(value) => value
case XmlBigInt(value) => value.doubleValue
case XmlBigDecimal(value) => value.doubleValue
}

final def toFloat: Float = this match {
case XmlByte(value) => value.toFloat
case XmlShort(value) => value.toFloat
case XmlInt(value) => value.toFloat
case XmlLong(value) => value.toFloat
case XmlFloat(value) => value
case XmlDouble(value) => value.toFloat
case XmlBigInt(value) => value.floatValue
case XmlBigDecimal(value) => value.floatValue
}

Expand All @@ -94,7 +110,10 @@ object XmlData {
}

final def toLong: Option[Long] = this match {
case XmlLong(value) => Some(value)
case XmlByte(value) => Some(value.toLong)
case XmlShort(value) => Some(value.toLong)
case XmlInt(value) => Some(value.toLong)
case XmlLong(value) => Some(value)
case XmlFloat(value) =>
Option(new JavaBigDecimal(java.lang.Float.toString(value)))
.filter(XmlNumber.bigDecimalIsValidLong)
Expand All @@ -103,6 +122,7 @@ object XmlData {
Option(JavaBigDecimal.valueOf(value))
.filter(XmlNumber.bigDecimalIsValidLong)
.map(_.longValue)
case XmlBigInt(value) => Try(value.toLong).toOption
case XmlBigDecimal(value) => Try(value.toLongExact).toOption
}
}
Expand All @@ -120,9 +140,13 @@ object XmlData {
) <= 0
}

private[xml] final case class XmlByte(value: Byte) extends XmlNumber
private[xml] final case class XmlShort(value: Short) extends XmlNumber
private[xml] final case class XmlInt(value: Int) extends XmlNumber
private[xml] final case class XmlLong(value: Long) extends XmlNumber
private[xml] final case class XmlFloat(value: Float) extends XmlNumber
private[xml] final case class XmlDouble(value: Double) extends XmlNumber
private[xml] final case class XmlBigInt(value: BigInt) extends XmlNumber
private[xml] final case class XmlBigDecimal(value: BigDecimal) extends XmlNumber

// ------------------------------------//
Expand All @@ -132,14 +156,21 @@ object XmlData {
case XmlChar(value) => value.toString
case XmlBool(value) => value.toString
case XmlArray(value) => value.mkString(",")
case XmlByte(value) => value.toString
case XmlShort(value) => value.toString
case XmlInt(value) => value.toString
case XmlLong(value) => value.toString
case XmlFloat(value) => value.toString
case XmlDouble(value) => value.toString
case XmlBigInt(value) => value.toString
case XmlBigDecimal(value) => value.toString
}

implicit val order: Order[XmlNumber] = {
Order.from {
case (XmlByte(x), XmlByte(y)) => x.compareTo(y)
case (XmlShort(x), XmlShort(y)) => x.compareTo(y)
case (XmlInt(x), XmlInt(y)) => x.compareTo(y)
case (XmlLong(x), XmlLong(y)) => x.compareTo(y)
case (XmlDouble(x), XmlDouble(y)) => java.lang.Double.compare(x, y)
case (XmlFloat(x), XmlFloat(y)) => java.lang.Float.compare(x, y)
Expand Down
77 changes: 77 additions & 0 deletions core/src/test/scala/cats/xml/XmlDataSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package cats.xml

import cats.xml.testing.VeryLongNumericString
import org.scalacheck.Arbitrary
import org.scalacheck.Prop.forAll

import scala.reflect.ClassTag

class XmlDataSuite extends munit.ScalaCheckSuite {

// fromNumberString
testFromNumberString[Int]()
testFromNumberString[Short]()
testFromNumberString[Long]()
testFromNumberString[Float]()
testFromNumberString[Double]()
testFromNumberString[BigInt]()
testFromNumberString[BigDecimal]()
testFromNumberString[String](shouldFail = true)
testFromNumberString[VeryLongNumericString](shouldFail = true)

// fromDataString
testFromDataString[String]
testFromDataString[Char]
testFromDataString[Boolean]
testFromDataString[Int]
testFromDataString[Short]
testFromDataString[Long]
testFromDataString[Float]
testFromDataString[Double]
testFromDataString[BigInt]
testFromDataString[BigDecimal]
testFromDataString[VeryLongNumericString]

private def testFromNumberString[T: Arbitrary](shouldFail: Boolean = false)(implicit

Check notice

Code scanning / Codacy-scalameta-pro (reported by Codacy)

Values should have minimum scope to avoid misuses.

testFromNumberString should have a smaller scope by using private[this]
c: ClassTag[T]
): Unit =
if (shouldFail)
property(s"Xml.fromNumberString return None with ${c.runtimeClass.getSimpleName}") {
forAll { (value: T) =>
assertEquals(
obtained = Xml.fromNumberString(value.toString),
expected = None
)
}
}
else
property(s"Xml.fromNumberString works with ${c.runtimeClass.getSimpleName}") {
forAll { (value: T) =>
assertEquals(
Xml.fromNumberString(value.toString).get.toBigDecimal,

Check notice

Code scanning / Codacy-scalameta-pro (reported by Codacy)

Calling get should be avoided on Option and Either.

Usage of get on optional type.
Some(BigDecimal(value.toString))
)
}
}

private def testFromDataString[T: Arbitrary](implicit

Check notice

Code scanning / Codacy-scalameta-pro (reported by Codacy)

Values should have minimum scope to avoid misuses.

testFromDataString should have a smaller scope by using private[this]
c: ClassTag[T]
): Unit =
property(s"Xml.fromDataString works with ${c.runtimeClass.getSimpleName}") {
forAll { (value: T) =>
Xml.fromDataString(value.toString) match {
case number: XmlData.XmlNumber =>
assertEquals(
number.toBigDecimal,
Some(BigDecimal(value.toString))
)
case other =>

Check notice

Code scanning / Codacy-scalameta-pro (reported by Codacy)

Prohibit case statement pattern match from being lowercase.

Lower case pattern matching.
assertEquals(
obtained = other.toString,
expected = value.toString
)
}

}
}
}
14 changes: 0 additions & 14 deletions core/src/test/scala/cats/xml/XmlNumberSuite.scala
Original file line number Diff line number Diff line change
@@ -1,14 +0,0 @@
package cats.xml

//TODO
class XmlNumberSuite extends munit.ScalaCheckSuite {

test("") {

Console.println(Xml.fromDataString("5340595900475325933418219074917").getClass)
assertEquals(
obtained = Xml.fromDataString("5340595900475325933418219074917"),
expected = Xml.ofString("5340595900475325933418219074917")
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package cats.xml.testing

import org.scalacheck.{Arbitrary, Gen}

case class VeryLongNumericString private (str: String) extends AnyVal
object VeryLongNumericString {

private[VeryLongNumericString] def apply(str: String): VeryLongNumericString =
new VeryLongNumericString(str)

implicit val arb: Arbitrary[VeryLongNumericString] =
Arbitrary(
Gen.numStr.suchThat(_.length > 31).map(VeryLongNumericString(_))
)
}