Skip to content

Commit

Permalink
Merge pull request #98 from geirolz/fix_long_numbers_problem
Browse files Browse the repository at this point in the history
Fix long numbers problem
  • Loading branch information
geirolz authored Jun 3, 2023
2 parents 7045e8c + 8331b94 commit 3c4b930
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 25 deletions.
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)
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 =>
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
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,
Some(BigDecimal(value.toString))
)
}
}

private def testFromDataString[T: Arbitrary](implicit
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 =>
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")
)
}
}
15 changes: 15 additions & 0 deletions core/src/test/scala/cats/xml/testing/VeryLongNumericString.scala
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(_))
)
}

0 comments on commit 3c4b930

Please sign in to comment.