Compare commits

..

2 Commits

16 changed files with 203 additions and 126 deletions

View File

@ -8,9 +8,9 @@ Import it when you need decimal arithmetic that should not inherit `Real`'s bina
import lyng.decimal import lyng.decimal
``` ```
## What `BigDecimal` Is For ## What `Decimal` Is For
Use `BigDecimal` when values are fundamentally decimal: Use `Decimal` when values are fundamentally decimal:
- money - money
- human-entered quantities - human-entered quantities
@ -38,8 +38,8 @@ assertEquals("2.2", c.toStringExpanded())
The three forms mean different things: The three forms mean different things:
- `1.d`: convert `Int -> BigDecimal` - `1.d`: convert `Int -> Decimal`
- `2.2.d`: convert `Real -> BigDecimal` - `2.2.d`: convert `Real -> Decimal`
- `"2.2".d`: parse exact decimal text - `"2.2".d`: parse exact decimal text
That distinction is intentional. That distinction is intentional.
@ -67,16 +67,36 @@ The explicit factory methods are:
```lyng ```lyng
import lyng.decimal import lyng.decimal
BigDecimal.fromInt(10) Decimal.fromInt(10)
BigDecimal.fromReal(2.5) Decimal.fromReal(2.5)
BigDecimal.fromString("12.34") Decimal.fromString("12.34")
``` ```
These are equivalent to the conversion-property forms, but sometimes clearer in APIs or generated code. These are equivalent to the conversion-property forms, but sometimes clearer in APIs or generated code.
## From Kotlin
If you already have an ionspin `BigDecimal` on the host side, the simplest supported way to create a Lyng `Decimal` is:
```kotlin
import com.ionspin.kotlin.bignum.decimal.BigDecimal
import net.sergeych.lyng.Script
import net.sergeych.lyng.asFacade
import net.sergeych.lyng.newDecimal
val scope = Script.newScope()
val decimal = scope.asFacade().newDecimal(BigDecimal.parseStringWithMode("12.34"))
```
Notes:
- `newDecimal(...)` loads `lyng.decimal` if needed
- it returns a real Lyng `Decimal` object instance
- this is the preferred Kotlin-side construction path when you already hold a host `BigDecimal`
## Core Operations ## Core Operations
`BigDecimal` supports: `Decimal` supports:
- `+` - `+`
- `-` - `-`
@ -115,7 +135,7 @@ assert(2 == 2.d)
assert(3 > 2.d) assert(3 > 2.d)
``` ```
Without this registration mechanism, only the cases directly implemented on the left-hand class would work. The bridge fills the gap for expressions such as `Int + BigDecimal` and `Real + BigDecimal`. Without this registration mechanism, only the cases directly implemented on the left-hand class would work. The bridge fills the gap for expressions such as `Int + Decimal` and `Real + Decimal`.
See [OperatorInterop.md](OperatorInterop.md) for the generic mechanism behind that. See [OperatorInterop.md](OperatorInterop.md) for the generic mechanism behind that.
@ -226,7 +246,7 @@ assertEquals("-0.12", withDecimalContext(2, DecimalRounding.HalfTowardsZero) { (
## Decimal With Stdlib Math Functions ## Decimal With Stdlib Math Functions
Core math helpers such as `abs`, `floor`, `ceil`, `round`, `sin`, `exp`, `ln`, `sqrt`, `log10`, `log2`, and `pow` Core math helpers such as `abs`, `floor`, `ceil`, `round`, `sin`, `exp`, `ln`, `sqrt`, `log10`, `log2`, and `pow`
now also accept `BigDecimal`. now also accept `Decimal`.
Current behavior is intentionally split: Current behavior is intentionally split:
@ -235,7 +255,7 @@ Current behavior is intentionally split:
- `floor(x)` - `floor(x)`
- `ceil(x)` - `ceil(x)`
- `round(x)` - `round(x)`
- `pow(x, y)` when `x` is `BigDecimal` and `y` is an integral exponent - `pow(x, y)` when `x` is `Decimal` and `y` is an integral exponent
- temporary bridge through `Real`: - temporary bridge through `Real`:
- `sin`, `cos`, `tan` - `sin`, `cos`, `tan`
- `asin`, `acos`, `atan` - `asin`, `acos`, `atan`
@ -248,7 +268,7 @@ Current behavior is intentionally split:
The temporary bridge is: The temporary bridge is:
```lyng ```lyng
BigDecimal -> Real -> host math -> BigDecimal Decimal -> Real -> host math -> Decimal
``` ```
This is a compatibility step, not the long-term design. Native decimal implementations will replace these bridge-based This is a compatibility step, not the long-term design. Native decimal implementations will replace these bridge-based
@ -259,12 +279,12 @@ Examples:
```lyng ```lyng
import lyng.decimal import lyng.decimal
assertEquals("2.5", (abs("-2.5".d) as BigDecimal).toStringExpanded()) assertEquals("2.5", (abs("-2.5".d) as Decimal).toStringExpanded())
assertEquals("2", (floor("2.9".d) as BigDecimal).toStringExpanded()) assertEquals("2", (floor("2.9".d) as Decimal).toStringExpanded())
// Temporary Real bridge: // Temporary Real bridge:
assertEquals((exp(1.25) as Real).d.toStringExpanded(), (exp("1.25".d) as BigDecimal).toStringExpanded()) assertEquals((exp(1.25) as Real).d.toStringExpanded(), (exp("1.25".d) as Decimal).toStringExpanded())
assertEquals((sqrt(2.0) as Real).d.toStringExpanded(), (sqrt("2".d) as BigDecimal).toStringExpanded()) assertEquals((sqrt(2.0) as Real).d.toStringExpanded(), (sqrt("2".d) as Decimal).toStringExpanded())
``` ```
If you care about exact decimal source text: If you care about exact decimal source text:

View File

@ -160,15 +160,15 @@ import lyng.decimal
3 > 2.d 3 > 2.d
``` ```
work naturally even though `Int` and `Real` themselves were not edited to know `BigDecimal`. work naturally even though `Int` and `Real` themselves were not edited to know `Decimal`.
The shape is: The shape is:
- `leftClass = Int` or `Real` - `leftClass = Int` or `Real`
- `rightClass = BigDecimal` - `rightClass = Decimal`
- `commonClass = BigDecimal` - `commonClass = Decimal`
- convert built-ins into `BigDecimal` - convert built-ins into `Decimal`
- leave `BigDecimal` values unchanged - leave `Decimal` values unchanged
## Step-By-Step Pattern For Your Own Type ## Step-By-Step Pattern For Your Own Type

View File

@ -15,9 +15,9 @@ Sources: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt`, `lynglib/s
- Preconditions: `require`, `check`. - Preconditions: `require`, `check`.
- Async/concurrency: `launch`, `yield`, `flow`, `delay`. - Async/concurrency: `launch`, `yield`, `flow`, `delay`.
- Math: `floor`, `ceil`, `round`, `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh`, `exp`, `ln`, `log10`, `log2`, `pow`, `sqrt`, `abs`, `clamp`. - Math: `floor`, `ceil`, `round`, `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh`, `exp`, `ln`, `log10`, `log2`, `pow`, `sqrt`, `abs`, `clamp`.
- These helpers also accept `lyng.decimal.BigDecimal`. - These helpers also accept `lyng.decimal.Decimal`.
- Exact Decimal path today: `abs`, `floor`, `ceil`, `round`, and `pow` with integral exponent. - Exact Decimal path today: `abs`, `floor`, `ceil`, `round`, and `pow` with integral exponent.
- Temporary Decimal path for the rest: convert `BigDecimal -> Real`, compute, then convert back to `BigDecimal`. - Temporary Decimal path for the rest: convert `Decimal -> Real`, compute, then convert back to `Decimal`.
- Treat that bridge as temporary; prefer native Decimal implementations when they become available. - Treat that bridge as temporary; prefer native Decimal implementations when they become available.
## 3. Core Global Constants/Types ## 3. Core Global Constants/Types
@ -60,6 +60,9 @@ Sources: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt`, `lynglib/s
## 5. Additional Built-in Modules (import explicitly) ## 5. Additional Built-in Modules (import explicitly)
- `import lyng.observable` - `import lyng.observable`
- `Observable`, `Subscription`, `ObservableList`, `ListChange` and change subtypes, `ChangeRejectionException`. - `Observable`, `Subscription`, `ObservableList`, `ListChange` and change subtypes, `ChangeRejectionException`.
- `import lyng.decimal`
- `Decimal`, `DecimalContext`, `DecimalRounding`, `withDecimalContext(...)`.
- Kotlin host helper: `ScopeFacade.newDecimal(BigDecimal)` wraps an ionspin host decimal as a Lyng `Decimal`.
- `import lyng.complex` - `import lyng.complex`
- `Complex`, `complex(re, im)`, `cis(angle)`, and numeric embedding extensions such as `2.i` / `3.re`. - `Complex`, `complex(re, im)`, `cis(angle)`, and numeric embedding extensions such as `2.i` / `3.re`.
- `import lyng.matrix` - `import lyng.matrix`

View File

@ -62,9 +62,9 @@ but:
The following functions return the argument unchanged if it is `Int`. The following functions return the argument unchanged if it is `Int`.
For `BigDecimal`: For `Decimal`:
- `floor(x)`, `ceil(x)`, and `round(x)` currently use exact decimal operations - `floor(x)`, `ceil(x)`, and `round(x)` currently use exact decimal operations
- the result stays `BigDecimal` - the result stays `Decimal`
For `Real`, the result is a transformed `Real`. For `Real`, the result is a transformed `Real`.
@ -78,11 +78,11 @@ For `Real`, the result is a transformed `Real`.
## Lyng math functions ## Lyng math functions
Decimal note: Decimal note:
- all scalar math helpers accept `BigDecimal` - all scalar math helpers accept `Decimal`
- `abs(x)` stays exact for `BigDecimal` - `abs(x)` stays exact for `Decimal`
- `pow(x, y)` is exact for `BigDecimal` when `y` is an integral exponent - `pow(x, y)` is exact for `Decimal` when `y` is an integral exponent
- the remaining `BigDecimal` cases currently use a temporary bridge: - the remaining `Decimal` cases currently use a temporary bridge:
`BigDecimal -> Real -> host math -> BigDecimal` `Decimal -> Real -> host math -> Decimal`
- this is temporary; native decimal implementations are planned - this is temporary; native decimal implementations are planned
| name | meaning | | name | meaning |
@ -104,7 +104,7 @@ Decimal note:
| log10(x) | $log_{10}(x)$ | | log10(x) | $log_{10}(x)$ |
| pow(x, y) | ${x^y}$ | | pow(x, y) | ${x^y}$ |
| sqrt(x) | $ \sqrt {x}$ | | sqrt(x) | $ \sqrt {x}$ |
| abs(x) | absolute value of x. Int if x is Int, BigDecimal if x is BigDecimal, Real otherwise | | abs(x) | absolute value of x. Int if x is Int, Decimal if x is Decimal, Real otherwise |
| clamp(x, range) | limit x to be inside range boundaries | | clamp(x, range) | limit x to be inside range boundaries |
For example: For example:
@ -120,9 +120,9 @@ For example:
import lyng.decimal import lyng.decimal
// Decimal-aware math works too. Some functions are exact, some still bridge through Real temporarily: // Decimal-aware math works too. Some functions are exact, some still bridge through Real temporarily:
assert( (abs("-2.5".d) as BigDecimal).toStringExpanded() == "2.5" ) assert( (abs("-2.5".d) as Decimal).toStringExpanded() == "2.5" )
assert( (floor("2.9".d) as BigDecimal).toStringExpanded() == "2" ) assert( (floor("2.9".d) as Decimal).toStringExpanded() == "2" )
assert( sin("0.5".d) is BigDecimal ) assert( sin("0.5".d) is Decimal )
// clamp() limits value to the range: // clamp() limits value to the range:
assert( clamp(15, 0..10) == 10 ) assert( clamp(15, 0..10) == 10 )

View File

@ -57,7 +57,7 @@ Lyng now ships a first-class decimal module built as a regular extension library
It provides: It provides:
- `BigDecimal` - `Decimal`
- convenient `.d` conversions from `Int`, `Real`, and `String` - convenient `.d` conversions from `Int`, `Real`, and `String`
- mixed arithmetic with `Int` and `Real` - mixed arithmetic with `Int` and `Real`
- local division precision and rounding control via `withDecimalContext(...)` - local division precision and rounding control via `withDecimalContext(...)`

View File

@ -17,6 +17,7 @@
package net.sergeych.lyng package net.sergeych.lyng
import com.ionspin.kotlin.bignum.decimal.BigDecimal
import net.sergeych.lyng.obj.* import net.sergeych.lyng.obj.*
/** /**
@ -133,3 +134,6 @@ fun ScopeFacade.raiseIllegalOperation(message: String = "Operation is illegal"):
fun ScopeFacade.raiseIterationFinished(): Nothing = fun ScopeFacade.raiseIterationFinished(): Nothing =
raiseError(ObjIterationFinishedException(requireScope())) raiseError(ObjIterationFinishedException(requireScope()))
suspend fun ScopeFacade.newDecimal(value: BigDecimal): ObjInstance =
ObjDecimalSupport.newDecimal(this, value)

View File

@ -253,7 +253,7 @@ class Script(
companion object { companion object {
private suspend fun ScopeFacade.numberToDouble(value: Obj): Double = private suspend fun ScopeFacade.numberToDouble(value: Obj): Double =
ObjBigDecimalSupport.toDoubleOrNull(value) ?: value.toDouble() ObjDecimalSupport.toDoubleOrNull(value) ?: value.toDouble()
private suspend fun ScopeFacade.decimalAwareUnaryMath( private suspend fun ScopeFacade.decimalAwareUnaryMath(
value: Obj, value: Obj,
@ -263,9 +263,9 @@ class Script(
exactDecimal?.let { exact -> exactDecimal?.let { exact ->
exact(value)?.let { return it } exact(value)?.let { return it }
} }
if (ObjBigDecimalSupport.isDecimalValue(value)) { if (ObjDecimalSupport.isDecimalValue(value)) {
return ObjBigDecimalSupport.fromRealLike(this, value, fallback(numberToDouble(value))) return ObjDecimalSupport.fromRealLike(this, value, fallback(numberToDouble(value)))
?: raiseIllegalState("failed to convert Real result back to BigDecimal") ?: raiseIllegalState("failed to convert Real result back to Decimal")
} }
return ObjReal(fallback(numberToDouble(value))) return ObjReal(fallback(numberToDouble(value)))
} }
@ -281,11 +281,11 @@ class Script(
} }
private suspend fun ScopeFacade.decimalAwarePow(base: Obj, exponent: Obj): Obj { private suspend fun ScopeFacade.decimalAwarePow(base: Obj, exponent: Obj): Obj {
ObjBigDecimalSupport.exactPow(this, base, exponent)?.let { return it } ObjDecimalSupport.exactPow(this, base, exponent)?.let { return it }
if (ObjBigDecimalSupport.isDecimalValue(base) || ObjBigDecimalSupport.isDecimalValue(exponent)) { if (ObjDecimalSupport.isDecimalValue(base) || ObjDecimalSupport.isDecimalValue(exponent)) {
return ObjBigDecimalSupport.fromRealLike(this, base, numberToDouble(base).pow(numberToDouble(exponent))) return ObjDecimalSupport.fromRealLike(this, base, numberToDouble(base).pow(numberToDouble(exponent)))
?: ObjBigDecimalSupport.fromRealLike(this, exponent, numberToDouble(base).pow(numberToDouble(exponent))) ?: ObjDecimalSupport.fromRealLike(this, exponent, numberToDouble(base).pow(numberToDouble(exponent)))
?: raiseIllegalState("failed to convert Real pow result back to BigDecimal") ?: raiseIllegalState("failed to convert Real pow result back to Decimal")
} }
return ObjReal(numberToDouble(base).pow(numberToDouble(exponent))) return ObjReal(numberToDouble(base).pow(numberToDouble(exponent)))
} }
@ -327,15 +327,15 @@ class Script(
} }
addFn("floor") { addFn("floor") {
val x = args.firstAndOnly() val x = args.firstAndOnly()
decimalAwareRoundLike(x, ObjBigDecimalSupport::exactFloor, ::floor) decimalAwareRoundLike(x, ObjDecimalSupport::exactFloor, ::floor)
} }
addFn("ceil") { addFn("ceil") {
val x = args.firstAndOnly() val x = args.firstAndOnly()
decimalAwareRoundLike(x, ObjBigDecimalSupport::exactCeil, ::ceil) decimalAwareRoundLike(x, ObjDecimalSupport::exactCeil, ::ceil)
} }
addFn("round") { addFn("round") {
val x = args.firstAndOnly() val x = args.firstAndOnly()
decimalAwareRoundLike(x, ObjBigDecimalSupport::exactRound, ::round) decimalAwareRoundLike(x, ObjDecimalSupport::exactRound, ::round)
} }
addFn("sin") { addFn("sin") {
@ -401,7 +401,7 @@ class Script(
addFn("abs") { addFn("abs") {
val x = args.firstAndOnly() val x = args.firstAndOnly()
if (x is ObjInt) ObjInt(x.value.absoluteValue) if (x is ObjInt) ObjInt(x.value.absoluteValue)
else decimalAwareUnaryMath(x, ObjBigDecimalSupport::exactAbs) { it.absoluteValue } else decimalAwareUnaryMath(x, ObjDecimalSupport::exactAbs) { it.absoluteValue }
} }
addFnDoc( addFnDoc(
@ -661,15 +661,15 @@ class Script(
) )
ensureFn("floor") { ensureFn("floor") {
val x = args.firstAndOnly() val x = args.firstAndOnly()
decimalAwareRoundLike(x, ObjBigDecimalSupport::exactFloor, ::floor) decimalAwareRoundLike(x, ObjDecimalSupport::exactFloor, ::floor)
} }
ensureFn("ceil") { ensureFn("ceil") {
val x = args.firstAndOnly() val x = args.firstAndOnly()
decimalAwareRoundLike(x, ObjBigDecimalSupport::exactCeil, ::ceil) decimalAwareRoundLike(x, ObjDecimalSupport::exactCeil, ::ceil)
} }
ensureFn("round") { ensureFn("round") {
val x = args.firstAndOnly() val x = args.firstAndOnly()
decimalAwareRoundLike(x, ObjBigDecimalSupport::exactRound, ::round) decimalAwareRoundLike(x, ObjDecimalSupport::exactRound, ::round)
} }
ensureFn("sin") { ensureFn("sin") {
decimalAwareUnaryMath(args.firstAndOnly(), fallback = ::sin) decimalAwareUnaryMath(args.firstAndOnly(), fallback = ::sin)
@ -729,7 +729,7 @@ class Script(
ensureFn("abs") { ensureFn("abs") {
val x = args.firstAndOnly() val x = args.firstAndOnly()
if (x is ObjInt) ObjInt(x.value.absoluteValue) if (x is ObjInt) ObjInt(x.value.absoluteValue)
else decimalAwareUnaryMath(x, ObjBigDecimalSupport::exactAbs) { it.absoluteValue } else decimalAwareUnaryMath(x, ObjDecimalSupport::exactAbs) { it.absoluteValue }
} }
} }
} }
@ -867,7 +867,7 @@ class Script(
} }
addPackage("lyng.decimal") { module -> addPackage("lyng.decimal") { module ->
module.eval(Source("lyng.decimal", decimalLyng)) module.eval(Source("lyng.decimal", decimalLyng))
ObjBigDecimalSupport.bindTo(module) ObjDecimalSupport.bindTo(module)
} }
addPackage("lyng.matrix") { module -> addPackage("lyng.matrix") { module ->
module.eval(Source("lyng.matrix", matrixLyng)) module.eval(Source("lyng.matrix", matrixLyng))

View File

@ -448,37 +448,37 @@ private fun buildStdlibDocs(): List<MiniDecl> {
// Math helpers (scalar versions) // Math helpers (scalar versions)
fun math1(name: String) = mod.funDoc( fun math1(name: String) = mod.funDoc(
name = name, name = name,
doc = StdlibInlineDocIndex.topFunDoc(name) ?: "Compute $name(x). Accepts Int, Real, and BigDecimal. BigDecimal currently uses a temporary Real bridge and will get native decimal implementations later.", doc = StdlibInlineDocIndex.topFunDoc(name) ?: "Compute $name(x). Accepts Int, Real, and Decimal. Decimal currently uses a temporary Real bridge and will get native decimal implementations later.",
params = listOf(ParamDoc("x", type("lyng.Number"))) params = listOf(ParamDoc("x", type("lyng.Number")))
) )
math1("sin"); math1("cos"); math1("tan"); math1("asin"); math1("acos"); math1("atan") math1("sin"); math1("cos"); math1("tan"); math1("asin"); math1("acos"); math1("atan")
mod.funDoc(name = "floor", doc = StdlibInlineDocIndex.topFunDoc("floor") ?: "Round down the number to the nearest integer. BigDecimal is handled directly and stays BigDecimal.", params = listOf(ParamDoc("x", type("lyng.Number")))) mod.funDoc(name = "floor", doc = StdlibInlineDocIndex.topFunDoc("floor") ?: "Round down the number to the nearest integer. Decimal is handled directly and stays Decimal.", params = listOf(ParamDoc("x", type("lyng.Number"))))
mod.funDoc(name = "ceil", doc = StdlibInlineDocIndex.topFunDoc("ceil") ?: "Round up the number to the nearest integer. BigDecimal is handled directly and stays BigDecimal.", params = listOf(ParamDoc("x", type("lyng.Number")))) mod.funDoc(name = "ceil", doc = StdlibInlineDocIndex.topFunDoc("ceil") ?: "Round up the number to the nearest integer. Decimal is handled directly and stays Decimal.", params = listOf(ParamDoc("x", type("lyng.Number"))))
mod.funDoc(name = "round", doc = StdlibInlineDocIndex.topFunDoc("round") ?: "Round the number to the nearest integer. BigDecimal is handled directly and stays BigDecimal.", params = listOf(ParamDoc("x", type("lyng.Number")))) mod.funDoc(name = "round", doc = StdlibInlineDocIndex.topFunDoc("round") ?: "Round the number to the nearest integer. Decimal is handled directly and stays Decimal.", params = listOf(ParamDoc("x", type("lyng.Number"))))
// Hyperbolic and inverse hyperbolic // Hyperbolic and inverse hyperbolic
math1("sinh"); math1("cosh"); math1("tanh"); math1("asinh"); math1("acosh"); math1("atanh") math1("sinh"); math1("cosh"); math1("tanh"); math1("asinh"); math1("acosh"); math1("atanh")
// Exponentials and logarithms // Exponentials and logarithms
mod.funDoc(name = "exp", doc = StdlibInlineDocIndex.topFunDoc("exp") ?: "Euler's exponential e^x. BigDecimal currently uses a temporary Real bridge and will get a native decimal implementation later.", params = listOf(ParamDoc("x", type("lyng.Number")))) mod.funDoc(name = "exp", doc = StdlibInlineDocIndex.topFunDoc("exp") ?: "Euler's exponential e^x. Decimal currently uses a temporary Real bridge and will get a native decimal implementation later.", params = listOf(ParamDoc("x", type("lyng.Number"))))
mod.funDoc(name = "ln", doc = StdlibInlineDocIndex.topFunDoc("ln") ?: "Natural logarithm (base e). BigDecimal currently uses a temporary Real bridge and will get a native decimal implementation later.", params = listOf(ParamDoc("x", type("lyng.Number")))) mod.funDoc(name = "ln", doc = StdlibInlineDocIndex.topFunDoc("ln") ?: "Natural logarithm (base e). Decimal currently uses a temporary Real bridge and will get a native decimal implementation later.", params = listOf(ParamDoc("x", type("lyng.Number"))))
mod.funDoc(name = "log10", doc = StdlibInlineDocIndex.topFunDoc("log10") ?: "Logarithm base 10. BigDecimal currently uses a temporary Real bridge and will get a native decimal implementation later.", params = listOf(ParamDoc("x", type("lyng.Number")))) mod.funDoc(name = "log10", doc = StdlibInlineDocIndex.topFunDoc("log10") ?: "Logarithm base 10. Decimal currently uses a temporary Real bridge and will get a native decimal implementation later.", params = listOf(ParamDoc("x", type("lyng.Number"))))
mod.funDoc(name = "log2", doc = StdlibInlineDocIndex.topFunDoc("log2") ?: "Logarithm base 2. BigDecimal currently uses a temporary Real bridge and will get a native decimal implementation later.", params = listOf(ParamDoc("x", type("lyng.Number")))) mod.funDoc(name = "log2", doc = StdlibInlineDocIndex.topFunDoc("log2") ?: "Logarithm base 2. Decimal currently uses a temporary Real bridge and will get a native decimal implementation later.", params = listOf(ParamDoc("x", type("lyng.Number"))))
// Power/roots and absolute value // Power/roots and absolute value
mod.funDoc( mod.funDoc(
name = "pow", name = "pow",
doc = StdlibInlineDocIndex.topFunDoc("pow") ?: "Raise `x` to the power `y`. BigDecimal with integral `y` is handled directly; other BigDecimal cases currently use a temporary Real bridge.", doc = StdlibInlineDocIndex.topFunDoc("pow") ?: "Raise `x` to the power `y`. Decimal with integral `y` is handled directly; other Decimal cases currently use a temporary Real bridge.",
params = listOf(ParamDoc("x", type("lyng.Number")), ParamDoc("y", type("lyng.Number"))) params = listOf(ParamDoc("x", type("lyng.Number")), ParamDoc("y", type("lyng.Number")))
) )
mod.funDoc( mod.funDoc(
name = "sqrt", name = "sqrt",
doc = StdlibInlineDocIndex.topFunDoc("sqrt") ?: "Square root of `x`. BigDecimal currently uses a temporary Real bridge and will get a native decimal implementation later.", doc = StdlibInlineDocIndex.topFunDoc("sqrt") ?: "Square root of `x`. Decimal currently uses a temporary Real bridge and will get a native decimal implementation later.",
params = listOf(ParamDoc("x", type("lyng.Number"))) params = listOf(ParamDoc("x", type("lyng.Number")))
) )
mod.funDoc( mod.funDoc(
name = "abs", name = "abs",
doc = StdlibInlineDocIndex.topFunDoc("abs") ?: "Absolute value of a number. Int stays Int, and BigDecimal stays BigDecimal.", doc = StdlibInlineDocIndex.topFunDoc("abs") ?: "Absolute value of a number. Int stays Int, and Decimal stays Decimal.",
params = listOf(ParamDoc("x", type("lyng.Number"))) params = listOf(ParamDoc("x", type("lyng.Number")))
) )

View File

@ -26,16 +26,16 @@ import net.sergeych.lyng.miniast.type
import net.sergeych.lyng.requiredArg import net.sergeych.lyng.requiredArg
import com.ionspin.kotlin.bignum.decimal.BigDecimal as IonBigDecimal import com.ionspin.kotlin.bignum.decimal.BigDecimal as IonBigDecimal
object ObjBigDecimalSupport { object ObjDecimalSupport {
private const val decimalContextVar = "__lyng_decimal_context__" private const val decimalContextVar = "__lyng_decimal_context__"
// For Real -> BigDecimal, preserve the actual IEEE-754 Double value using a // For Real -> Decimal, preserve the actual IEEE-754 Double value using a
// round-trip-safe precision. This intentionally does not try to recover source text. // round-trip-safe precision. This intentionally does not try to recover source text.
private val realConversionMode = DecimalMode(17L, RoundingMode.ROUND_HALF_TO_EVEN) private val realConversionMode = DecimalMode(17L, RoundingMode.ROUND_HALF_TO_EVEN)
// Division needs an explicit stopping rule for non-terminating results. Use a // Division needs an explicit stopping rule for non-terminating results. Use a
// decimal128-like default context until Lyng exposes per-operation contexts. // decimal128-like default context until Lyng exposes per-operation contexts.
private val defaultDivisionMode = DecimalMode(34L, RoundingMode.ROUND_HALF_TO_EVEN) private val defaultDivisionMode = DecimalMode(34L, RoundingMode.ROUND_HALF_TO_EVEN)
private val zero: IonBigDecimal = IonBigDecimal.ZERO private val zero: IonBigDecimal = IonBigDecimal.ZERO
private val decimalTypeDecl = TypeDecl.Simple("lyng.decimal.BigDecimal", false) private val decimalTypeDecl = TypeDecl.Simple("lyng.decimal.Decimal", false)
private object BoundMarker private object BoundMarker
private data class DecimalRuntimeContext( private data class DecimalRuntimeContext(
val precision: Long, val precision: Long,
@ -43,7 +43,7 @@ object ObjBigDecimalSupport {
) : Obj() ) : Obj()
suspend fun bindTo(module: ModuleScope) { suspend fun bindTo(module: ModuleScope) {
val decimalClass = module.requireClass("BigDecimal") val decimalClass = module.requireClass("Decimal")
if (decimalClass.kotlinClassData === BoundMarker) return if (decimalClass.kotlinClassData === BoundMarker) return
decimalClass.kotlinClassData = BoundMarker decimalClass.kotlinClassData = BoundMarker
decimalClass.isAbstract = false decimalClass.isAbstract = false
@ -99,7 +99,7 @@ object ObjBigDecimalSupport {
try { try {
newInstance(decimalClass, IonBigDecimal.parseStringWithMode(value)) newInstance(decimalClass, IonBigDecimal.parseStringWithMode(value))
} catch (e: Throwable) { } catch (e: Throwable) {
requireScope().raiseIllegalArgument("invalid BigDecimal string: $value") requireScope().raiseIllegalArgument("invalid Decimal string: $value")
} }
} }
module.addFn("withDecimalContext") { module.addFn("withDecimalContext") {
@ -129,7 +129,7 @@ object ObjBigDecimalSupport {
} }
fun isDecimalValue(value: Obj): Boolean = fun isDecimalValue(value: Obj): Boolean =
value is ObjInstance && value.objClass.className == "BigDecimal" value is ObjInstance && value.objClass.className == "Decimal"
suspend fun exactAbs(scope: ScopeFacade, value: Obj): Obj? = suspend fun exactAbs(scope: ScopeFacade, value: Obj): Obj? =
decimalValueOrNull(value)?.let { scope.newInstanceLikeDecimal(value, it.abs()) } decimalValueOrNull(value)?.let { scope.newInstanceLikeDecimal(value, it.abs()) }
@ -159,8 +159,14 @@ object ObjBigDecimalSupport {
fun toDoubleOrNull(value: Obj): Double? = fun toDoubleOrNull(value: Obj): Double? =
decimalValueOrNull(value)?.doubleValue(false) decimalValueOrNull(value)?.doubleValue(false)
suspend fun newDecimal(scope: ScopeFacade, value: IonBigDecimal): ObjInstance {
val decimalModule = scope.requireScope().currentImportProvider.createModuleScope(scope.pos, "lyng.decimal")
val decimalClass = decimalModule.requireClass("Decimal")
return scope.newInstance(decimalClass, value)
}
private fun valueOf(obj: Obj): IonBigDecimal { private fun valueOf(obj: Obj): IonBigDecimal {
val instance = obj as? ObjInstance ?: error("BigDecimal receiver must be an object instance") val instance = obj as? ObjInstance ?: error("Decimal receiver must be an object instance")
return instance.kotlinInstanceData as? IonBigDecimal ?: zero return instance.kotlinInstanceData as? IonBigDecimal ?: zero
} }
@ -186,14 +192,14 @@ object ObjBigDecimalSupport {
private suspend fun ScopeFacade.newInstance(decimalClass: ObjClass, value: IonBigDecimal): ObjInstance { private suspend fun ScopeFacade.newInstance(decimalClass: ObjClass, value: IonBigDecimal): ObjInstance {
val instance = call(decimalClass) as? ObjInstance val instance = call(decimalClass) as? ObjInstance
?: raiseIllegalState("BigDecimal() did not return an object instance") ?: raiseIllegalState("Decimal() did not return an object instance")
instance.kotlinInstanceData = value instance.kotlinInstanceData = value
return instance return instance
} }
private suspend fun ScopeFacade.newInstanceLikeDecimal(sample: Obj, value: IonBigDecimal): ObjInstance { private suspend fun ScopeFacade.newInstanceLikeDecimal(sample: Obj, value: IonBigDecimal): ObjInstance {
val decimalClass = (sample as? ObjInstance)?.objClass val decimalClass = (sample as? ObjInstance)?.objClass
?: raiseIllegalState("BigDecimal sample must be an object instance") ?: raiseIllegalState("Decimal sample must be an object instance")
return newInstance(decimalClass, value) return newInstance(decimalClass, value)
} }
@ -201,12 +207,12 @@ object ObjBigDecimalSupport {
is ObjInt -> IonBigDecimal.fromLongAsSignificand(value.value) is ObjInt -> IonBigDecimal.fromLongAsSignificand(value.value)
is ObjReal -> IonBigDecimal.fromDouble(value.value, realConversionMode) is ObjReal -> IonBigDecimal.fromDouble(value.value, realConversionMode)
is ObjInstance -> { is ObjInstance -> {
if (value.objClass.className != "BigDecimal") { if (value.objClass.className != "Decimal") {
scope.raiseIllegalArgument("expected BigDecimal-compatible value, got ${value.objClass.className}") scope.raiseIllegalArgument("expected Decimal-compatible value, got ${value.objClass.className}")
} }
value.kotlinInstanceData as? IonBigDecimal ?: zero value.kotlinInstanceData as? IonBigDecimal ?: zero
} }
else -> scope.raiseIllegalArgument("expected BigDecimal-compatible value, got ${value.objClass.className}") else -> scope.raiseIllegalArgument("expected Decimal-compatible value, got ${value.objClass.className}")
} }
private suspend fun normalizeContext(scope: Scope, value: Obj): DecimalRuntimeContext { private suspend fun normalizeContext(scope: Scope, value: Obj): DecimalRuntimeContext {
@ -294,31 +300,31 @@ object ObjBigDecimalSupport {
private fun registerBuiltinConversions(decimalClass: ObjClass) { private fun registerBuiltinConversions(decimalClass: ObjClass) {
ObjInt.type.addPropertyDoc( ObjInt.type.addPropertyDoc(
name = "d", name = "d",
doc = "Convert this integer to a BigDecimal.", doc = "Convert this integer to a Decimal.",
type = type("lyng.decimal.BigDecimal"), type = type("lyng.decimal.Decimal"),
moduleName = "lyng.decimal", moduleName = "lyng.decimal",
getter = { newInstance(decimalClass, IonBigDecimal.fromLongAsSignificand(thisAs<ObjInt>().value)) } getter = { newInstance(decimalClass, IonBigDecimal.fromLongAsSignificand(thisAs<ObjInt>().value)) }
) )
ObjInt.type.members["d"] = ObjInt.type.members.getValue("d").copy(typeDecl = decimalTypeDecl) ObjInt.type.members["d"] = ObjInt.type.members.getValue("d").copy(typeDecl = decimalTypeDecl)
ObjReal.type.addPropertyDoc( ObjReal.type.addPropertyDoc(
name = "d", name = "d",
doc = "Convert this real number to a BigDecimal by preserving the current IEEE-754 value with 17 significant digits and half-even rounding.", doc = "Convert this real number to a Decimal by preserving the current IEEE-754 value with 17 significant digits and half-even rounding.",
type = type("lyng.decimal.BigDecimal"), type = type("lyng.decimal.Decimal"),
moduleName = "lyng.decimal", moduleName = "lyng.decimal",
getter = { newInstance(decimalClass, IonBigDecimal.fromDouble(thisAs<ObjReal>().value, realConversionMode)) } getter = { newInstance(decimalClass, IonBigDecimal.fromDouble(thisAs<ObjReal>().value, realConversionMode)) }
) )
ObjReal.type.members["d"] = ObjReal.type.members.getValue("d").copy(typeDecl = decimalTypeDecl) ObjReal.type.members["d"] = ObjReal.type.members.getValue("d").copy(typeDecl = decimalTypeDecl)
ObjString.type.addPropertyDoc( ObjString.type.addPropertyDoc(
name = "d", name = "d",
doc = "Parse this string as a BigDecimal.", doc = "Parse this string as a Decimal.",
type = type("lyng.decimal.BigDecimal"), type = type("lyng.decimal.Decimal"),
moduleName = "lyng.decimal", moduleName = "lyng.decimal",
getter = { getter = {
val value = thisAs<ObjString>().value val value = thisAs<ObjString>().value
try { try {
newInstance(decimalClass, IonBigDecimal.parseStringWithMode(value)) newInstance(decimalClass, IonBigDecimal.parseStringWithMode(value))
} catch (e: Throwable) { } catch (e: Throwable) {
requireScope().raiseIllegalArgument("invalid BigDecimal string: $value") requireScope().raiseIllegalArgument("invalid Decimal string: $value")
} }
} }
) )

View File

@ -587,6 +587,10 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
} }
override suspend fun compareTo(scope: Scope, other: Obj): Int { override suspend fun compareTo(scope: Scope, other: Obj): Int {
val explicitCompare = objClass.getInstanceMemberOrNull("compareTo", includeStatic = false)
if (explicitCompare != null) {
return invokeInstanceMethod(scope, "compareTo", Arguments(other)).cast<ObjInt>(scope).toInt()
}
if (other !is ObjInstance || other.objClass != objClass) { if (other !is ObjInstance || other.objClass != objClass) {
OperatorInteropRegistry.invokeCompare(scope, this, other)?.let { return it } OperatorInteropRegistry.invokeCompare(scope, this, other)?.let { return it }
return -1 return -1

View File

@ -48,7 +48,7 @@ enum DecimalRounding {
/** /**
* Dynamic decimal arithmetic settings. * Dynamic decimal arithmetic settings.
* *
* A decimal context is not attached permanently to a `BigDecimal` value. Instead, it is applied dynamically * A decimal context is not attached permanently to a `Decimal` value. Instead, it is applied dynamically
* inside `withDecimalContext(...)`, which makes the rule local to a block of code. * inside `withDecimalContext(...)`, which makes the rule local to a block of code.
* *
* Default context: * Default context:
@ -76,7 +76,7 @@ class DecimalContext(
/** /**
* Arbitrary-precision decimal value. * Arbitrary-precision decimal value.
* *
* `BigDecimal` is intended for decimal arithmetic where binary floating-point (`Real`) is the wrong tool: * `Decimal` is intended for decimal arithmetic where binary floating-point (`Real`) is the wrong tool:
* - money * - money
* - human-entered decimal values * - human-entered decimal values
* - ratios that should round in decimal, not in binary * - ratios that should round in decimal, not in binary
@ -84,10 +84,10 @@ class DecimalContext(
* *
* Creating values: * Creating values:
* *
* - `1.d` converts `Int -> BigDecimal` * - `1.d` converts `Int -> Decimal`
* - `2.2.d` converts `Real -> BigDecimal` by preserving the current IEEE-754 value * - `2.2.d` converts `Real -> Decimal` by preserving the current IEEE-754 value
* - `"2.2".d` parses exact decimal text * - `"2.2".d` parses exact decimal text
* - `BigDecimal.fromInt(...)`, `fromReal(...)`, `fromString(...)` are explicit factory forms * - `Decimal.fromInt(...)`, `fromReal(...)`, `fromString(...)` are explicit factory forms
* *
* Important distinction: * Important distinction:
* *
@ -109,7 +109,7 @@ class DecimalContext(
* *
* Mixed arithmetic: * Mixed arithmetic:
* *
* `BigDecimal` defines its own operators against decimal-compatible values, and the decimal module also registers * `Decimal` defines its own operators against decimal-compatible values, and the decimal module also registers
* interop bridges so built-in left-hand operands work naturally: * interop bridges so built-in left-hand operands work naturally:
* *
* import lyng.decimal * import lyng.decimal
@ -134,15 +134,15 @@ class DecimalContext(
* *
* "2.2".d * "2.2".d
* *
* That is the precise form. `2.2.d` remains a `Real -> BigDecimal` conversion by design. * That is the precise form. `2.2.d` remains a `Real -> Decimal` conversion by design.
*/ */
extern class BigDecimal() { extern class Decimal() {
/** Add another decimal-compatible value. */ /** Add another decimal-compatible value. */
extern fun plus(other: Object): BigDecimal extern fun plus(other: Object): Decimal
/** Subtract another decimal-compatible value. */ /** Subtract another decimal-compatible value. */
extern fun minus(other: Object): BigDecimal extern fun minus(other: Object): Decimal
/** Multiply by another decimal-compatible value. */ /** Multiply by another decimal-compatible value. */
extern fun mul(other: Object): BigDecimal extern fun mul(other: Object): Decimal
/** /**
* Divide by another decimal-compatible value. * Divide by another decimal-compatible value.
* *
@ -150,13 +150,13 @@ extern class BigDecimal() {
* - by default: `34` significant digits, `HalfEven` * - by default: `34` significant digits, `HalfEven`
* - inside `withDecimalContext(...)`: the context active for the current block * - inside `withDecimalContext(...)`: the context active for the current block
*/ */
extern fun div(other: Object): BigDecimal extern fun div(other: Object): Decimal
/** Remainder with another decimal-compatible value. */ /** Remainder with another decimal-compatible value. */
extern fun mod(other: Object): BigDecimal extern fun mod(other: Object): Decimal
/** Compare with another decimal-compatible value. */ /** Compare with another decimal-compatible value. */
extern fun compareTo(other: Object): Int extern fun compareTo(other: Object): Int
/** Unary minus. */ /** Unary minus. */
extern fun negate(): BigDecimal extern fun negate(): Decimal
/** Convert to `Int` by dropping the fractional part according to backend conversion rules. */ /** Convert to `Int` by dropping the fractional part according to backend conversion rules. */
extern fun toInt(): Int extern fun toInt(): Int
/** Convert to `Real`. */ /** Convert to `Real`. */
@ -169,16 +169,16 @@ extern class BigDecimal() {
extern fun toStringExpanded(): String extern fun toStringExpanded(): String
/** Create a decimal from an `Int`. */ /** Create a decimal from an `Int`. */
static extern fun fromInt(value: Int): BigDecimal static extern fun fromInt(value: Int): Decimal
/** /**
* Create a decimal from a `Real`. * Create a decimal from a `Real`.
* *
* This preserves the current IEEE-754 value using a round-trip-safe decimal conversion. * This preserves the current IEEE-754 value using a round-trip-safe decimal conversion.
* It does not try to recover the original source text. * It does not try to recover the original source text.
*/ */
static extern fun fromReal(value: Real): BigDecimal static extern fun fromReal(value: Real): Decimal
/** Parse exact decimal text. */ /** Parse exact decimal text. */
static extern fun fromString(value: String): BigDecimal static extern fun fromString(value: String): Decimal
} }
/** /**

View File

@ -97,7 +97,7 @@ enum BinaryOperator {
* The registry is symmetric for the converted values, but not for the original syntax. * The registry is symmetric for the converted values, but not for the original syntax.
* Its job is specifically to fill the gap where your custom type appears on the right: * Its job is specifically to fill the gap where your custom type appears on the right:
* *
* - `myDecimal + 1` usually already works if `BigDecimal.plus(Int)` exists * - `myDecimal + 1` usually already works if `Decimal.plus(Int)` exists
* - `1 + myDecimal` needs registration because `Int` itself is not rewritten * - `1 + myDecimal` needs registration because `Int` itself is not rewritten
* *
* Typical pattern for a custom type: * Typical pattern for a custom type:
@ -135,7 +135,7 @@ enum BinaryOperator {
* - `3 > Rational(5, 2)` works * - `3 > Rational(5, 2)` works
* - `2 == Rational(2, 1)` works * - `2 == Rational(2, 1)` works
* *
* Decimal uses the same mechanism internally to make `Int + BigDecimal` and `Real + BigDecimal` * Decimal uses the same mechanism internally to make `Int + Decimal` and `Real + Decimal`
* work without changing the built-in `Int` or `Real` classes. * work without changing the built-in `Int` or `Real` classes.
*/ */
extern object OperatorInterop { extern object OperatorInterop {

View File

@ -17,10 +17,12 @@
package net.sergeych.lyng package net.sergeych.lyng
import com.ionspin.kotlin.bignum.decimal.BigDecimal
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals
class BigDecimalModuleTest { class DecimalModuleTest {
@Test @Test
fun testDecimalModuleFactoriesAndConversions() = runTest { fun testDecimalModuleFactoriesAndConversions() = runTest {
val scope = Script.newScope() val scope = Script.newScope()
@ -28,8 +30,8 @@ class BigDecimalModuleTest {
""" """
import lyng.decimal import lyng.decimal
assertEquals("12.34", BigDecimal.fromString("12.34").toStringExpanded()) assertEquals("12.34", Decimal.fromString("12.34").toStringExpanded())
assertEquals("1", BigDecimal.fromInt(1).toStringExpanded()) assertEquals("1", Decimal.fromInt(1).toStringExpanded())
assertEquals("2.5", "2.5".d.toStringExpanded()) assertEquals("2.5", "2.5".d.toStringExpanded())
assertEquals("1", 1.d.toStringExpanded()) assertEquals("1", 1.d.toStringExpanded())
assertEquals("2.2", 2.2.d.toStringExpanded()) assertEquals("2.2", 2.2.d.toStringExpanded())
@ -154,14 +156,14 @@ class BigDecimalModuleTest {
""" """
import lyng.decimal import lyng.decimal
val absValue = abs("-2.5".d) as BigDecimal val absValue = abs("-2.5".d) as Decimal
val floorPos = floor("2.9".d) as BigDecimal val floorPos = floor("2.9".d) as Decimal
val floorNeg = floor("-2.1".d) as BigDecimal val floorNeg = floor("-2.1".d) as Decimal
val ceilPos = ceil("2.1".d) as BigDecimal val ceilPos = ceil("2.1".d) as Decimal
val ceilNeg = ceil("-2.1".d) as BigDecimal val ceilNeg = ceil("-2.1".d) as Decimal
val roundPos = round("2.5".d) as BigDecimal val roundPos = round("2.5".d) as Decimal
val roundNeg = round("-2.5".d) as BigDecimal val roundNeg = round("-2.5".d) as Decimal
val powInt = pow("1.5".d, 2) as BigDecimal val powInt = pow("1.5".d, 2) as Decimal
assertEquals("2.5", absValue.toStringExpanded()) assertEquals("2.5", absValue.toStringExpanded())
assertEquals("2", floorPos.toStringExpanded()) assertEquals("2", floorPos.toStringExpanded())
@ -182,13 +184,13 @@ class BigDecimalModuleTest {
""" """
import lyng.decimal import lyng.decimal
val sinDecimal = sin("0.5".d) as BigDecimal val sinDecimal = sin("0.5".d) as Decimal
val expDecimal = exp("1.25".d) as BigDecimal val expDecimal = exp("1.25".d) as Decimal
val sqrtDecimal = sqrt("2".d) as BigDecimal val sqrtDecimal = sqrt("2".d) as Decimal
val lnDecimal = ln("2".d) as BigDecimal val lnDecimal = ln("2".d) as Decimal
val log10Decimal = log10("2".d) as BigDecimal val log10Decimal = log10("2".d) as Decimal
val log2Decimal = log2("2".d) as BigDecimal val log2Decimal = log2("2".d) as Decimal
val powDecimal = pow("2".d, "0.5".d) as BigDecimal val powDecimal = pow("2".d, "0.5".d) as Decimal
assertEquals((sin(0.5) as Real).d.toStringExpanded(), sinDecimal.toStringExpanded()) assertEquals((sin(0.5) as Real).d.toStringExpanded(), sinDecimal.toStringExpanded())
assertEquals((exp(1.25) as Real).d.toStringExpanded(), expDecimal.toStringExpanded()) assertEquals((exp(1.25) as Real).d.toStringExpanded(), expDecimal.toStringExpanded())
@ -209,8 +211,8 @@ class BigDecimalModuleTest {
val decimal = 42.d val decimal = 42.d
val context = DecimalContext(12) val context = DecimalContext(12)
assert(decimal is BigDecimal) assert(decimal is Decimal)
assertEquals(BigDecimal, decimal::class) assertEquals(Decimal, decimal::class)
assert(context is DecimalContext) assert(context is DecimalContext)
assertEquals(DecimalContext, context::class) assertEquals(DecimalContext, context::class)
@ -226,4 +228,25 @@ class BigDecimalModuleTest {
assertEquals(53.d, X) assertEquals(53.d, X)
""") """)
} }
@Test
fun kotlinHelperCanWrapIonBigDecimal() = runTest {
val scope = Script.newScope()
val decimal = scope.asFacade().newDecimal(BigDecimal.parseStringWithMode("12.34"))
assertEquals("Decimal", decimal.objClass.className)
assertEquals("12.34", decimal.toString(scope).value)
assertEquals("12.34", decimal.invokeInstanceMethod(scope, "toStringExpanded").cast<net.sergeych.lyng.obj.ObjString>(scope).value)
}
@Test
fun testDecimalComparisons() = runTest {
eval("""
import lyng.decimal
val X = 42.d
assert(X < 43.d)
assert(X < 43)
assert(X == 42)
""".trimIndent())
}
} }

View File

@ -168,4 +168,21 @@ class MatrixModuleTest {
assertEquals(Vector, vectorValue::class) assertEquals(Vector, vectorValue::class)
""".trimIndent()) """.trimIndent())
} }
@Test
fun testMatrixAndVectorComparisons() = runTest {
eval("""
import lyng.matrix
val v0 = vector([1, 2, 3])
val v1 = vector([1, 2, 4])
assert(v0 < v1)
assert(v0 == vector([1, 2, 3]))
val m0 = matrix([[1, 2], [3, 4]])
val m1 = matrix([[1, 2], [3, 5]])
assert(m0 < m1)
assert(m0 == matrix([[1, 2], [3, 4]]))
""".trimIndent())
}
} }

View File

@ -79,10 +79,10 @@ extern class MapEntry<K,V> : Array<Object> {
// Built-in math helpers (implemented in host runtime). // Built-in math helpers (implemented in host runtime).
// //
// Decimal note: // Decimal note:
// - these helpers accept `BigDecimal` values too // - these helpers accept `Decimal` values too
// - `abs`, `floor`, `ceil`, `round`, and `pow(x, y)` with integral `y` keep decimal arithmetic // - `abs`, `floor`, `ceil`, `round`, and `pow(x, y)` with integral `y` keep decimal arithmetic
// - the remaining decimal cases currently use a temporary bridge: // - the remaining decimal cases currently use a temporary bridge:
// `BigDecimal -> Real -> host math -> BigDecimal` // `Decimal -> Real -> host math -> Decimal`
// - this is temporary and will be replaced with dedicated decimal implementations // - this is temporary and will be replaced with dedicated decimal implementations
extern fun abs(x: Object): Object extern fun abs(x: Object): Object
extern fun ln(x: Object): Object extern fun ln(x: Object): Object

View File

@ -1,8 +1,8 @@
# Decimal Math TODO # Decimal Math TODO
These stdlib math helpers currently accept `BigDecimal`, but some still use the temporary compatibility path These stdlib math helpers currently accept `Decimal`, but some still use the temporary compatibility path
`BigDecimal -> Real -> host math -> BigDecimal` `Decimal -> Real -> host math -> Decimal`
instead of a native decimal implementation. instead of a native decimal implementation.