Skip to content
Open
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Number is organized into multiple modules:
#### Current Version

This version is 1.10.1
The latest published version is 1.9.3 (see [HISTORY](docs/HISTORY.md)).
The latest published version is 1.10.5 (see [HISTORY](docs/HISTORY.md)).
That should match the version of the latest release on Maven Central in the badge at the top.

To use Number in your project, add the following dependency:
Expand All @@ -59,7 +59,7 @@ libraryDependencies += "com.phasmidsoftware" %% "number" % "1.9.3"
<dependency>
<groupId>com.phasmidsoftware</groupId>
<artifactId>number_3</artifactId>
<version>1.9.3</version>
<version>1.10.5</version>
</dependency>
```

Expand Down
5 changes: 4 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

ThisBuild / organization := "com.phasmidsoftware"

ThisBuild / version := "1.10.4"
ThisBuild / version := "1.10.5"

val scalaVersionNumber = "3.7.3"
val catsVersion = "2.13.0"
Expand All @@ -21,6 +21,9 @@ ThisBuild / libraryDependencies ++= Seq(
"ch.qos.logback" % "logback-classic" % logbackClassicVersion % "runtime"
)

// CONSIDER removing this (for performance reasons) if publishLocal runs fine without it.
Global / concurrentRestrictions += Tags.limitAll(1)

// ============================================================================
// COMPILER OPTIONS - Moderate settings focused on catching + operator issues
// ============================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ case object MonadicOperationExp extends MonadicOperation {
* This function operates within a `Try` monad to handle potential failures.
*/
private lazy val expRat: Rational => Try[Rational] = {
case r if r.isInfinity && r.signum < 0 =>
case r if r.isInfinite && r.signum < 0 =>
Success(Rational.zero)
case r =>
fail("can't do exp Rational=>Rational for non-zero parameter")(r)
Expand Down Expand Up @@ -845,7 +845,7 @@ case object QueryOperationIsInfinite extends QueryOperation[Boolean] {
*/
def getFunctions: BooleanQueryFunctions = new QueryFunctions[Boolean] {
val fInt: Int => Try[Boolean] = tryF[Int, Boolean](_ => false)
val fRat: Rational => Try[Boolean] = tryF[Rational, Boolean](x => x.isInfinity)
val fRat: Rational => Try[Boolean] = tryF[Rational, Boolean](x => x.isInfinite)
val fDouble: Double => Try[Boolean] = tryF[Double, Boolean](x => x == Double.PositiveInfinity || x == Double.NegativeInfinity)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ case class Rational private[inner](n: BigInt, d: BigInt) extends NumberLike {
* @return true if this NumberLike object is exact in the context of No factor, else false.
*/
lazy val isExact: Boolean =
!(isInfinity || isNaN)
!(isInfinite || isNaN)

/**
* Determines if the Rational object represents "not a number" (NaN).
Expand Down Expand Up @@ -421,17 +421,17 @@ case class Rational private[inner](n: BigInt, d: BigInt) extends NumberLike {
* @note Throws RationalException if the Rational number is infinity.
*/
lazy val forceToBigDecimal: BigDecimal =
if (!isInfinity)
if (!isInfinite)
BigDecimal(n) / BigDecimal(d)
else
throw RationalException(s"cannot convert infinity to BigDecimal: $this")

/**
* Method to determine if the given Rational represents an infinity.
* Method to determine if the given Rational represents an infinity (positive or negative).
*
* @return true if the denominator is zero; false otherwise.
*/
lazy val isInfinity: Boolean =
lazy val isInfinite: Boolean =
d == bigZero && n != bigZero

/**
Expand Down Expand Up @@ -531,7 +531,7 @@ case class Rational private[inner](n: BigInt, d: BigInt) extends NumberLike {
* @return the mediant of this and other.
*/
def mediant(other: Rational): Rational =
if (signum >= 0 && other.signum >= 0 && !isInfinity && !other.isInfinity)
if (signum >= 0 && other.signum >= 0 && !isInfinite && !other.isInfinite)
Rational(n + other.n, d + other.d)
else
NaN
Expand Down Expand Up @@ -615,7 +615,7 @@ case class Rational private[inner](n: BigInt, d: BigInt) extends NumberLike {
"NaN"
case _ if isZero && d < 0 =>
"-0"
case _ if isInfinity =>
case _ if isInfinite =>
(if (n > 0) "" else "-") + "∞"
case _ if isWhole =>
toBigInt.toString
Expand Down Expand Up @@ -1329,7 +1329,7 @@ object Rational {
if (x.isWhole)
Success(x.n)
else
Failure(RationalException(s"toBigInt: $x is " + (if (x.isInfinity)
Failure(RationalException(s"toBigInt: $x is " + (if (x.isInfinite)
"infinite" else "not whole")))

/**
Expand All @@ -1356,7 +1356,7 @@ object Rational {
*/// CONSIDER making this private or moving back into RationalSpec
def hasCorrectRatio(r: Rational, top: BigInt, bottom: BigInt): Boolean = {
val _a = r * bottom
val result = bottom == 0 || _a.isInfinity || (_a.isWhole && _a.toBigInt == top)
val result = bottom == 0 || _a.isInfinite || (_a.isWhole && _a.toBigInt == top)
if (!result)
throw RationalException(s"incorrect ratio: r=${r.n}/${r.d}, top=$top, bottom=$bottom, _a=${_a}, gcd=${top.gcd(bottom)}")
result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ abstract class BaseComplex(val real: Number, val imag: Number) extends Complex {
* @return the sum.
*/
def add(x: Field): Field =
sum(narrow(x, polar = false))
sum(narrow(x, polar = false)) // CONSIDER dealing with infinity.

/**
* Method to add this to the given parameter (a Cartesian).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,16 @@ object Constants {
*/
lazy val iPi: Complex = ComplexCartesian(0, Number.pi)

/**
* Represents a constant Field value of "Not a Number" (NaN).
* NaN is used to signify an undefined or unrepresentable value, especially
* in numerical computations that result in invalid operations like division by zero
* or operations involving undefined values.
*
* This value is of type Real and wraps the predefined NaN value from the Number type.
*/
lazy val NaN: Field = Real(Number.NaN)

/**
* Represents the square root of 2 as a field constant.
* CONSIDER making this a Complex.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,12 @@ trait Number extends Fuzz[Double] with Ordered[Number] with Numerical {
* @return the result of the multiplication as a Field
*/
def multiply(x: Field): Field = (this, x) match {
case (Constants.zero, _) if x.isInfinite =>
Constants.NaN
case (_, Constants.zero) if this.isInfinite =>
Constants.NaN
case (a, b) if a.isInfinite || b.isInfinite =>
Constants.infinity
case (Number.zero, _) | (_, Constants.zero) =>
Constants.zero
case (Number.one, _) =>
Expand Down Expand Up @@ -834,6 +840,13 @@ object Number {
* Exact value of i
*/
lazy val i: Number = ExactNumber(-1, SquareRoot)

/**
* Represents the mathematical concept of infinity as a lazy value of type `Number`.
* The value is constructed using an exact representation of infinity via the `ExactNumber` class.
*/
lazy val `∞`: Number = ExactNumber(Rational.infinity)

/**
* Exact value of the Number √2 (not Complex)
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ case class Real(x: Number) extends Field {
* @return the sum.
*/
def add(y: Field): Field = y match {
case field if this.isInfinite && field.isInfinite && signum != field.signum =>
Constants.NaN
case field if this.isInfinite || field.isInfinite =>
Constants.infinity
case multivariate: Multivariate =>
multivariate.add(this)
case Real(r) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class RationalSpec extends flatspec.AnyFlatSpec with should.Matchers with Privat
private val zeroSymbol = Symbol("zero")
private val toBigIntSymbol = Symbol("toBigInt")
private val wholeSymbol = Symbol("whole")
private val infinitySymbol = Symbol("infinity")
private val infinitySymbol = Symbol("infinite")
private val unitySymbol = Symbol("unity")
private val narrowSymbol = Symbol("narrow")
private val normalizeSymbol = Symbol("normalize")
Expand All @@ -34,7 +34,7 @@ class RationalSpec extends flatspec.AnyFlatSpec with should.Matchers with Privat
}
it should "yield infinity for 1, 0" in {
val r = new Rational(BigInt(1), 0L)
r.isInfinity shouldBe true
r.isInfinite shouldBe true
}
it should "yield NaN for 0, 0" in {
new Rational(BigInt(0), 0L).isNaN shouldBe true
Expand Down Expand Up @@ -101,18 +101,18 @@ class RationalSpec extends flatspec.AnyFlatSpec with should.Matchers with Privat
}
it should "work for 1, 0" in {
val r = Rational(1, 0)
r.isInfinity shouldBe true
r.isInfinite shouldBe true
r shouldBe Rational.infinity
r.render shouldBe "∞"
}
it should "work for -1, 0" in {
val r = Rational(-1, 0)
r.isInfinity shouldBe true
r.isInfinite shouldBe true
r.render shouldBe "-∞"
}
it should "work for -1, -2" in {
val r = Rational(-1, -2)
r.isInfinity shouldBe false
r.isInfinite shouldBe false
r shouldBe Rational.half
}
it should "work for -2624712818L, -1" in {
Expand Down Expand Up @@ -829,7 +829,7 @@ class RationalSpec extends flatspec.AnyFlatSpec with should.Matchers with Privat
behavior of "r-interpolator"
it should "work for -1/0" in {
val r = r"-1/0"
r.isInfinity shouldBe true
r.isInfinite shouldBe true
r shouldBe Rational(-1, 0)
}
it should "work for 1/-2147483648" in {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
package com.phasmidsoftware.number.core.numerical

import com.phasmidsoftware.number.core.inner.PureNumber
import com.phasmidsoftware.number.core.inner.{PureNumber, Rational}
import com.phasmidsoftware.number.core.numerical.Real.RealIsFractional.mkNumericOps
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should

class FieldSpec extends AnyFlatSpec with should.Matchers {

behavior of "Field"
// it should "isExact" in {
// val x = (ConstPi / 2).materialize
// x.isExact shouldBe true
// }
// it should "multiply i by itself correctly" in {
// val z = Expression(Constants.i) * Constants.i
// val result = z.materialize
// result shouldBe Constants.minusOne
// }
it should "isExact" in {
val x = (Constants.pi / Real(2))
x.isExact shouldBe true
}
it should "multiply i by itself correctly" in {
val z = Constants.i * Constants.i
z shouldBe ComplexCartesian(-1, 0)
}
it should "add" in {
val one = Number.one
val result = one `add` Constants.i
Expand All @@ -31,4 +31,29 @@ class FieldSpec extends AnyFlatSpec with should.Matchers {
x should matchPattern { case Real(FuzzyNumber(Left(Left(Some(0.6931471805599453))), PureNumber, _)) => }
x.render shouldBe "0.69314718055994530[89]"
}
it should "add infinity to infinity" in {
val result = Constants.infinity `add` Constants.infinity
result shouldBe Constants.infinity
}
it should "add infinity to negative infinity" in {
val result = Constants.infinity `add` Real(Rational(-1).invert)
result shouldBe Constants.infinity
}
it should "add infinity to a finite number" in {
val result = Constants.infinity `add` Real(1)
result shouldBe Constants.infinity
}
it should "multiply infinity by zero" in {
val result = Constants.infinity `multiply` Constants.infinity
result shouldBe Constants.infinity
}
it should "multiply infinity by a finite number" in {
val result = Constants.infinity `multiply` Real(1)
result shouldBe Constants.infinity
}
it should "divide infinity by infinity" in {
val result = Constants.infinity `divide` Constants.infinity
result shouldBe Constants.NaN
}

}
2 changes: 1 addition & 1 deletion docs/HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Migration and Version History

## Versions

* Version 1.10.5: Fixed various issues (mostly regarding infinity) exposed by the EML expansion.
* Version 1.10.1: Fixed issue with over-precise Rational numbers.
* Version 1.10.0: Improved the Mermaid diagrams, see [docs/diagrams/index.md](docs/diagrams/index.md).
* Version 1.9.3:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ case class Aggregate(function: ExpressionBiFunction, xs: Seq[Expression]) extend
case Product =>
x => if x < 1 then 1 / x else x
case _ =>
throw new IllegalArgumentException("complementaryTermsEliminatorAggregate: Power function not supported")
throw new IllegalArgumentException("eliminateComplementaryTerms: Power function not supported")
}

// NOTE this ordering is really only appropriate when f is Sum.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
package com.phasmidsoftware.number.expression.expr

import com.phasmidsoftware.number.algebra.core.*
import com.phasmidsoftware.number.algebra.eager.{Complex, Eager, Real}
import com.phasmidsoftware.number.algebra.eager.{Complex, Eager, InversePower, Real}
import com.phasmidsoftware.number.core.inner.Factor
import com.phasmidsoftware.number.expression.expr.Expression.em

Expand Down Expand Up @@ -148,6 +148,16 @@ case class Noop(w: String) extends AtomicExpression {
def evaluate(context: Context): Option[Eager] =
throw new UnsupportedOperationException(s"Can''t evaluate: $this")

/**
* Represents a lazy evaluation result of this `Noop` expression as-is, without any modification or processing.
*
* This method is overridden and fixed to always return `None`, indicating that
* the `Noop` expression does not inherently evaluate to any meaningful result.
*
* @return an `Option[Eager]`, which is always `None`.
*/
override lazy val evaluateAsIs: Option[Eager] = None

/**
* Method to render this Renderable in a presentable manner.
*
Expand Down Expand Up @@ -207,5 +217,6 @@ case class Noop(w: String) extends AtomicExpression {
}

object Noop {
def apply: Noop = Noop("NaN")
val TEST_STRING = "Error tester"
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import cats.implicits.catsSyntaxEq
import com.phasmidsoftware.number.algebra.core.*
import com.phasmidsoftware.number.algebra.eager
import com.phasmidsoftware.number.algebra.eager.Eager.eagerToField
import com.phasmidsoftware.number.algebra.eager.{Angle, Complex, Eager, InversePower, IsInteger, NaturalExponential, QuadraticSolution, RationalNumber, Structure, WholeNumber}
import com.phasmidsoftware.number.algebra.eager.{Angle, Complex, Eager, InversePower, IsInteger, QuadraticSolution, RationalNumber, Structure, WholeNumber}
import com.phasmidsoftware.number.core.inner.{Factor, PureNumber, Radian, Rational}
import com.phasmidsoftware.number.core.numerical
import com.phasmidsoftware.number.core.numerical.{ComplexCartesian, ComplexPolar, Number, Real, Complex as CoreComplex}
Expand Down Expand Up @@ -109,8 +109,17 @@ case class BiFunction(a: Expression, b: Expression, f: ExpressionBiFunction) ext
*/
lazy val structuralMatcher: em.AutoMatcher[Expression] = em.Matcher[Expression, Expression]("BiFunction:structuralMatcher") {
// NOTE: Canonical ordering — swap commutative operands if out of order.
case BiFunction(Noop(_), _, _) | BiFunction(_, Noop(_), _) =>
em.Match(Noop.apply)
case BiFunction(Infinity, UniFunction(Infinity, Negate), Sum) | BiFunction(UniFunction(Infinity, Negate), Infinity, Sum) =>
em.Match(Noop.apply)
case IsCommutative(x, y, f) if summon[Ordering[Expression]].gt(x, y) =>
em.Match(BiFunction(y, x, f))
em.Match(BiFunction(y, x, f)).flatMap(structuralMatcher)
case BiFunction(Zero, Infinity, Product) | BiFunction(Infinity, Zero, Product) =>
em.Match(Noop.apply)
// NOTE: any other BiFunction involving Infinity results in Infinity
case BiFunction(Infinity, _, _) | BiFunction(_, Infinity, _) =>
em.Match(Infinity)
case BiFunction(a, b, Sum) if a == b =>
em.Match(a * Two)
case BiFunction(BiFunction(w, x, Power), BiFunction(y, z, Power), Product) if w == y =>
Expand Down Expand Up @@ -435,7 +444,7 @@ case class BiFunction(a: Expression, b: Expression, f: ExpressionBiFunction) ext
case (E, ValueExpression(Complex(c), _)) if c.isImaginary && c.modulus == numerical.Number.pi =>
em.Match(MinusOne)
case (E, ValueExpression(v: eager.Number, _)) =>
em.Match(Literal(NaturalExponential(v)))
em.Match(UniFunction(v, Exp))
case (E, UniFunction(x, Ln)) =>
em.Match(x)
case (E, BiFunction(x, E, Log)) =>
Expand Down
Loading