Handle non-finite real/decimal arithmetic
This commit is contained in:
parent
f845213332
commit
850efedb72
@ -1079,6 +1079,7 @@ interface Numeric {
|
|||||||
|
|
||||||
fun Obj.toDouble(): Double =
|
fun Obj.toDouble(): Double =
|
||||||
(this as? Numeric)?.doubleValue
|
(this as? Numeric)?.doubleValue
|
||||||
|
?: ObjDecimalSupport.toDoubleOrNull(this)
|
||||||
?: (this as? ObjString)?.value?.toDouble()
|
?: (this as? ObjString)?.value?.toDouble()
|
||||||
?: throw IllegalArgumentException("cannot convert to double $this")
|
?: throw IllegalArgumentException("cannot convert to double $this")
|
||||||
|
|
||||||
|
|||||||
@ -54,30 +54,36 @@ object ObjDecimalSupport {
|
|||||||
instance.kotlinInstanceData = zero
|
instance.kotlinInstanceData = zero
|
||||||
}
|
}
|
||||||
decimalClass.addFn("plus") {
|
decimalClass.addFn("plus") {
|
||||||
ObjComplexSupport.decimalBinary(this, thisObj, args.firstAndOnly(), InteropOperator.Plus)
|
mixedRealDecimalArithmeticFallback(thisObj, args.firstAndOnly(), InteropOperator.Plus)
|
||||||
|
?: ObjComplexSupport.decimalBinary(this, thisObj, args.firstAndOnly(), InteropOperator.Plus)
|
||||||
?: OperatorInteropRegistry.invokeBinary(requireScope(), thisObj, args.firstAndOnly(), InteropOperator.Plus)
|
?: OperatorInteropRegistry.invokeBinary(requireScope(), thisObj, args.firstAndOnly(), InteropOperator.Plus)
|
||||||
?: newInstance(decimalClass, valueOf(thisObj).plus(coerceArg(requireScope(), args.firstAndOnly())))
|
?: newInstance(decimalClass, valueOf(thisObj).plus(coerceArg(requireScope(), args.firstAndOnly())))
|
||||||
}
|
}
|
||||||
decimalClass.addFn("minus") {
|
decimalClass.addFn("minus") {
|
||||||
ObjComplexSupport.decimalBinary(this, thisObj, args.firstAndOnly(), InteropOperator.Minus)
|
mixedRealDecimalArithmeticFallback(thisObj, args.firstAndOnly(), InteropOperator.Minus)
|
||||||
|
?: ObjComplexSupport.decimalBinary(this, thisObj, args.firstAndOnly(), InteropOperator.Minus)
|
||||||
?: OperatorInteropRegistry.invokeBinary(requireScope(), thisObj, args.firstAndOnly(), InteropOperator.Minus)
|
?: OperatorInteropRegistry.invokeBinary(requireScope(), thisObj, args.firstAndOnly(), InteropOperator.Minus)
|
||||||
?: newInstance(decimalClass, valueOf(thisObj).minus(coerceArg(requireScope(), args.firstAndOnly())))
|
?: newInstance(decimalClass, valueOf(thisObj).minus(coerceArg(requireScope(), args.firstAndOnly())))
|
||||||
}
|
}
|
||||||
decimalClass.addFn("mul") {
|
decimalClass.addFn("mul") {
|
||||||
ObjComplexSupport.decimalBinary(this, thisObj, args.firstAndOnly(), InteropOperator.Mul)
|
mixedRealDecimalArithmeticFallback(thisObj, args.firstAndOnly(), InteropOperator.Mul)
|
||||||
|
?: ObjComplexSupport.decimalBinary(this, thisObj, args.firstAndOnly(), InteropOperator.Mul)
|
||||||
?: OperatorInteropRegistry.invokeBinary(requireScope(), thisObj, args.firstAndOnly(), InteropOperator.Mul)
|
?: OperatorInteropRegistry.invokeBinary(requireScope(), thisObj, args.firstAndOnly(), InteropOperator.Mul)
|
||||||
?: newInstance(decimalClass, valueOf(thisObj).times(coerceArg(requireScope(), args.firstAndOnly())))
|
?: newInstance(decimalClass, valueOf(thisObj).times(coerceArg(requireScope(), args.firstAndOnly())))
|
||||||
}
|
}
|
||||||
decimalClass.addFn("div") {
|
decimalClass.addFn("div") {
|
||||||
ObjComplexSupport.decimalBinary(this, thisObj, args.firstAndOnly(), InteropOperator.Div)
|
mixedRealDecimalArithmeticFallback(thisObj, args.firstAndOnly(), InteropOperator.Div)
|
||||||
|
?: ObjComplexSupport.decimalBinary(this, thisObj, args.firstAndOnly(), InteropOperator.Div)
|
||||||
?: OperatorInteropRegistry.invokeBinary(requireScope(), thisObj, args.firstAndOnly(), InteropOperator.Div)
|
?: OperatorInteropRegistry.invokeBinary(requireScope(), thisObj, args.firstAndOnly(), InteropOperator.Div)
|
||||||
?: newInstance(decimalClass, divideWithContext(valueOf(thisObj), coerceArg(requireScope(), args.firstAndOnly()), currentDivisionMode(requireScope())))
|
?: newInstance(decimalClass, divideWithContext(valueOf(thisObj), coerceArg(requireScope(), args.firstAndOnly()), currentDivisionMode(requireScope())))
|
||||||
}
|
}
|
||||||
decimalClass.addFn("mod") {
|
decimalClass.addFn("mod") {
|
||||||
newInstance(decimalClass, valueOf(thisObj).rem(coerceArg(requireScope(), args.firstAndOnly())))
|
mixedRealDecimalArithmeticFallback(thisObj, args.firstAndOnly(), InteropOperator.Mod)
|
||||||
|
?: newInstance(decimalClass, valueOf(thisObj).rem(coerceArg(requireScope(), args.firstAndOnly())))
|
||||||
}
|
}
|
||||||
decimalClass.addFn("compareTo") {
|
decimalClass.addFn("compareTo") {
|
||||||
ObjInt.of(valueOf(thisObj).compareTo(coerceArg(requireScope(), args.firstAndOnly())).toLong())
|
mixedRealDecimalCompareFallback(thisObj, args.firstAndOnly())?.toObj()
|
||||||
|
?: ObjInt.of(valueOf(thisObj).compareTo(coerceArg(requireScope(), args.firstAndOnly())).toLong())
|
||||||
}
|
}
|
||||||
decimalClass.addFn("negate") {
|
decimalClass.addFn("negate") {
|
||||||
newInstance(decimalClass, valueOf(thisObj).unaryMinus())
|
newInstance(decimalClass, valueOf(thisObj).unaryMinus())
|
||||||
@ -100,7 +106,7 @@ object ObjDecimalSupport {
|
|||||||
}
|
}
|
||||||
decimalClass.addClassFn("fromReal") {
|
decimalClass.addClassFn("fromReal") {
|
||||||
val value = requiredArg<ObjReal>(0).value
|
val value = requiredArg<ObjReal>(0).value
|
||||||
newInstance(decimalClass, IonBigDecimal.fromDouble(value, realConversionMode))
|
newInstanceFromFiniteReal(decimalClass, value)
|
||||||
}
|
}
|
||||||
decimalClass.addClassFn("fromString") {
|
decimalClass.addClassFn("fromString") {
|
||||||
val value = requiredArg<ObjString>(0).value
|
val value = requiredArg<ObjString>(0).value
|
||||||
@ -161,12 +167,35 @@ object ObjDecimalSupport {
|
|||||||
|
|
||||||
suspend fun fromRealLike(scope: ScopeFacade, sample: Obj, value: Double): Obj? {
|
suspend fun fromRealLike(scope: ScopeFacade, sample: Obj, value: Double): Obj? {
|
||||||
if (!isDecimalValue(sample)) return null
|
if (!isDecimalValue(sample)) return null
|
||||||
|
if (!value.isFinite()) return ObjReal.of(value)
|
||||||
return scope.newInstanceLikeDecimal(sample, IonBigDecimal.fromDouble(value, realConversionMode))
|
return scope.newInstanceLikeDecimal(sample, IonBigDecimal.fromDouble(value, realConversionMode))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toDoubleOrNull(value: Obj): Double? =
|
fun toDoubleOrNull(value: Obj): Double? =
|
||||||
decimalValueOrNull(value)?.doubleValue(false)
|
decimalValueOrNull(value)?.doubleValue(false)
|
||||||
|
|
||||||
|
internal fun mixedRealDecimalArithmeticFallback(left: Obj, right: Obj, operator: InteropOperator): Obj? {
|
||||||
|
if (!isMixedRealDecimal(left, right)) return null
|
||||||
|
val leftDouble = numericDoubleOrNull(left) ?: return null
|
||||||
|
val rightDouble = numericDoubleOrNull(right) ?: return null
|
||||||
|
val result = when (operator) {
|
||||||
|
InteropOperator.Plus -> leftDouble + rightDouble
|
||||||
|
InteropOperator.Minus -> leftDouble - rightDouble
|
||||||
|
InteropOperator.Mul -> leftDouble * rightDouble
|
||||||
|
InteropOperator.Div -> leftDouble / rightDouble
|
||||||
|
InteropOperator.Mod -> leftDouble % rightDouble
|
||||||
|
else -> return null
|
||||||
|
}
|
||||||
|
return if (result.isFinite()) null else ObjReal.of(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun mixedRealDecimalCompareFallback(left: Obj, right: Obj): Int? {
|
||||||
|
if (!isMixedRealDecimal(left, right)) return null
|
||||||
|
val leftDouble = numericDoubleOrNull(left) ?: return null
|
||||||
|
val rightDouble = numericDoubleOrNull(right) ?: return null
|
||||||
|
return if (leftDouble.isFinite() && rightDouble.isFinite()) null else leftDouble.compareTo(rightDouble)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun newDecimal(scope: ScopeFacade, value: IonBigDecimal): ObjInstance {
|
suspend fun newDecimal(scope: ScopeFacade, value: IonBigDecimal): ObjInstance {
|
||||||
val decimalModule = scope.requireScope().currentImportProvider.createModuleScope(scope.pos, "lyng.decimal")
|
val decimalModule = scope.requireScope().currentImportProvider.createModuleScope(scope.pos, "lyng.decimal")
|
||||||
val decimalClass = decimalModule.requireClass("Decimal")
|
val decimalClass = decimalModule.requireClass("Decimal")
|
||||||
@ -205,6 +234,13 @@ object ObjDecimalSupport {
|
|||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun ScopeFacade.newInstanceFromFiniteReal(decimalClass: ObjClass, value: Double): ObjInstance {
|
||||||
|
if (!value.isFinite()) {
|
||||||
|
requireScope().raiseIllegalArgument("cannot convert non-finite Real to Decimal: $value")
|
||||||
|
}
|
||||||
|
return newInstance(decimalClass, IonBigDecimal.fromDouble(value, realConversionMode))
|
||||||
|
}
|
||||||
|
|
||||||
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("Decimal sample must be an object instance")
|
?: raiseIllegalState("Decimal sample must be an object instance")
|
||||||
@ -213,7 +249,12 @@ object ObjDecimalSupport {
|
|||||||
|
|
||||||
private fun coerceArg(scope: Scope, value: Obj): IonBigDecimal = when (value) {
|
private fun coerceArg(scope: Scope, value: Obj): IonBigDecimal = when (value) {
|
||||||
is ObjInt -> IonBigDecimal.fromLong(value.value)
|
is ObjInt -> IonBigDecimal.fromLong(value.value)
|
||||||
is ObjReal -> IonBigDecimal.fromDouble(value.value, realConversionMode)
|
is ObjReal -> {
|
||||||
|
if (!value.value.isFinite()) {
|
||||||
|
scope.raiseIllegalArgument("cannot convert non-finite Real to Decimal: ${value.value}")
|
||||||
|
}
|
||||||
|
IonBigDecimal.fromDouble(value.value, realConversionMode)
|
||||||
|
}
|
||||||
is ObjInstance -> {
|
is ObjInstance -> {
|
||||||
if (value.objClass.className != "Decimal") {
|
if (value.objClass.className != "Decimal") {
|
||||||
scope.raiseIllegalArgument("expected Decimal-compatible value, got ${value.objClass.className}")
|
scope.raiseIllegalArgument("expected Decimal-compatible value, got ${value.objClass.className}")
|
||||||
@ -319,7 +360,7 @@ object ObjDecimalSupport {
|
|||||||
doc = "Convert this real number to a Decimal 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.Decimal"),
|
type = type("lyng.decimal.Decimal"),
|
||||||
moduleName = "lyng.decimal",
|
moduleName = "lyng.decimal",
|
||||||
getter = { newInstance(decimalClass, IonBigDecimal.fromDouble(thisAs<ObjReal>().value, realConversionMode)) }
|
getter = { newInstanceFromFiniteReal(decimalClass, thisAs<ObjReal>().value) }
|
||||||
)
|
)
|
||||||
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(
|
||||||
@ -340,26 +381,48 @@ object ObjDecimalSupport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun registerInterop(decimalClass: ObjClass) {
|
private fun registerInterop(decimalClass: ObjClass) {
|
||||||
|
val decimalIdentity = ObjExternCallable.fromBridge {
|
||||||
|
requiredArg<Obj>(0)
|
||||||
|
}
|
||||||
|
val numericOperators = listOf(
|
||||||
|
InteropOperator.Plus.name,
|
||||||
|
InteropOperator.Minus.name,
|
||||||
|
InteropOperator.Mul.name,
|
||||||
|
InteropOperator.Div.name,
|
||||||
|
InteropOperator.Mod.name,
|
||||||
|
InteropOperator.Compare.name,
|
||||||
|
InteropOperator.Equals.name
|
||||||
|
)
|
||||||
OperatorInteropRegistry.register(
|
OperatorInteropRegistry.register(
|
||||||
leftClass = ObjInt.type,
|
leftClass = ObjInt.type,
|
||||||
rightClass = decimalClass,
|
rightClass = decimalClass,
|
||||||
commonClass = decimalClass,
|
commonClass = decimalClass,
|
||||||
operatorNames = listOf(
|
operatorNames = numericOperators,
|
||||||
InteropOperator.Plus.name,
|
|
||||||
InteropOperator.Minus.name,
|
|
||||||
InteropOperator.Mul.name,
|
|
||||||
InteropOperator.Div.name,
|
|
||||||
InteropOperator.Mod.name,
|
|
||||||
InteropOperator.Compare.name,
|
|
||||||
InteropOperator.Equals.name
|
|
||||||
),
|
|
||||||
leftToCommon = ObjExternCallable.fromBridge {
|
leftToCommon = ObjExternCallable.fromBridge {
|
||||||
val value = requiredArg<ObjInt>(0).value
|
val value = requiredArg<ObjInt>(0).value
|
||||||
newInstance(decimalClass, IonBigDecimal.fromLong(value))
|
newInstance(decimalClass, IonBigDecimal.fromLong(value))
|
||||||
},
|
},
|
||||||
rightToCommon = ObjExternCallable.fromBridge {
|
rightToCommon = decimalIdentity
|
||||||
requiredArg<Obj>(0)
|
)
|
||||||
}
|
OperatorInteropRegistry.register(
|
||||||
|
leftClass = ObjReal.type,
|
||||||
|
rightClass = decimalClass,
|
||||||
|
commonClass = decimalClass,
|
||||||
|
operatorNames = numericOperators,
|
||||||
|
leftToCommon = ObjExternCallable.fromBridge {
|
||||||
|
val value = requiredArg<ObjReal>(0).value
|
||||||
|
newInstanceFromFiniteReal(decimalClass, value)
|
||||||
|
},
|
||||||
|
rightToCommon = decimalIdentity
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isMixedRealDecimal(left: Obj, right: Obj): Boolean =
|
||||||
|
(left is ObjReal && isDecimalValue(right)) || (right is ObjReal && isDecimalValue(left))
|
||||||
|
|
||||||
|
private fun numericDoubleOrNull(value: Obj): Double? = when (value) {
|
||||||
|
is Numeric -> value.doubleValue
|
||||||
|
is ObjInstance -> toDoubleOrNull(value)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,6 +43,7 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
|||||||
|
|
||||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
if (other is ObjReal) return value.compareTo(other.value)
|
if (other is ObjReal) return value.compareTo(other.value)
|
||||||
|
ObjDecimalSupport.mixedRealDecimalCompareFallback(this, other)?.let { return it }
|
||||||
OperatorInteropRegistry.invokeCompare(scope, this, other)?.let { return it }
|
OperatorInteropRegistry.invokeCompare(scope, this, other)?.let { return it }
|
||||||
if (other !is Numeric) return -2
|
if (other !is Numeric) return -2
|
||||||
return value.compareTo(other.doubleValue)
|
return value.compareTo(other.doubleValue)
|
||||||
@ -67,23 +68,28 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun plus(scope: Scope, other: Obj): Obj =
|
override suspend fun plus(scope: Scope, other: Obj): Obj =
|
||||||
OperatorInteropRegistry.invokeBinary(scope, this, other, InteropOperator.Plus)
|
ObjDecimalSupport.mixedRealDecimalArithmeticFallback(this, other, InteropOperator.Plus)
|
||||||
|
?: OperatorInteropRegistry.invokeBinary(scope, this, other, InteropOperator.Plus)
|
||||||
?: of(this.value + other.toDouble())
|
?: of(this.value + other.toDouble())
|
||||||
|
|
||||||
override suspend fun minus(scope: Scope, other: Obj): Obj =
|
override suspend fun minus(scope: Scope, other: Obj): Obj =
|
||||||
OperatorInteropRegistry.invokeBinary(scope, this, other, InteropOperator.Minus)
|
ObjDecimalSupport.mixedRealDecimalArithmeticFallback(this, other, InteropOperator.Minus)
|
||||||
|
?: OperatorInteropRegistry.invokeBinary(scope, this, other, InteropOperator.Minus)
|
||||||
?: of(this.value - other.toDouble())
|
?: of(this.value - other.toDouble())
|
||||||
|
|
||||||
override suspend fun mul(scope: Scope, other: Obj): Obj =
|
override suspend fun mul(scope: Scope, other: Obj): Obj =
|
||||||
OperatorInteropRegistry.invokeBinary(scope, this, other, InteropOperator.Mul)
|
ObjDecimalSupport.mixedRealDecimalArithmeticFallback(this, other, InteropOperator.Mul)
|
||||||
|
?: OperatorInteropRegistry.invokeBinary(scope, this, other, InteropOperator.Mul)
|
||||||
?: of(this.value * other.toDouble())
|
?: of(this.value * other.toDouble())
|
||||||
|
|
||||||
override suspend fun div(scope: Scope, other: Obj): Obj =
|
override suspend fun div(scope: Scope, other: Obj): Obj =
|
||||||
OperatorInteropRegistry.invokeBinary(scope, this, other, InteropOperator.Div)
|
ObjDecimalSupport.mixedRealDecimalArithmeticFallback(this, other, InteropOperator.Div)
|
||||||
|
?: OperatorInteropRegistry.invokeBinary(scope, this, other, InteropOperator.Div)
|
||||||
?: of(this.value / other.toDouble())
|
?: of(this.value / other.toDouble())
|
||||||
|
|
||||||
override suspend fun mod(scope: Scope, other: Obj): Obj =
|
override suspend fun mod(scope: Scope, other: Obj): Obj =
|
||||||
OperatorInteropRegistry.invokeBinary(scope, this, other, InteropOperator.Mod)
|
ObjDecimalSupport.mixedRealDecimalArithmeticFallback(this, other, InteropOperator.Mod)
|
||||||
|
?: OperatorInteropRegistry.invokeBinary(scope, this, other, InteropOperator.Mod)
|
||||||
?: of(this.value % other.toDouble())
|
?: of(this.value % other.toDouble())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -5460,4 +5460,34 @@ class ScriptTest {
|
|||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Test
|
||||||
|
// fun testFromCalcrus1() = runTest {
|
||||||
|
// eval($$"""
|
||||||
|
// import lyng.decimal
|
||||||
|
// var x = 7.0.d
|
||||||
|
// // глубина по звуку падения
|
||||||
|
// val m = 1 // kg
|
||||||
|
// val d = 0.06 // 6 cm
|
||||||
|
// val c = 340 // скор. звука
|
||||||
|
// val g = 9.82
|
||||||
|
// var cnt = 0
|
||||||
|
// var h = 0.0
|
||||||
|
// var t = x
|
||||||
|
// var message = ""
|
||||||
|
//
|
||||||
|
// while(true){
|
||||||
|
// val h0 = 0
|
||||||
|
// h = c*c/h*(1 + g*t/c -sqrt(1+2*g*t/c))
|
||||||
|
// message = "iter ${cnt++}"
|
||||||
|
// if( cnt > 100 ) {
|
||||||
|
// message= "ошибка"
|
||||||
|
// break 0
|
||||||
|
// }
|
||||||
|
// x = h
|
||||||
|
// if( abs(h-h0)/h > 0.08 ) break h
|
||||||
|
// }
|
||||||
|
// println(x)
|
||||||
|
// """.trimIndent())
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
import com.ionspin.kotlin.bignum.decimal.BigDecimal
|
import com.ionspin.kotlin.bignum.decimal.BigDecimal
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import net.sergeych.lyng.obj.ObjException
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
@ -77,6 +78,94 @@ class DecimalModuleTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDecimalModuleMixedRealOperators() = runTest {
|
||||||
|
val scope = Script.newScope()
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
import lyng.decimal
|
||||||
|
|
||||||
|
assertEquals(3.5.d, 1.5 + 2.d)
|
||||||
|
assertEquals(3.5.d, 2.d + 1.5)
|
||||||
|
assertEquals(1.5.d, 3.5 - 2.d)
|
||||||
|
assertEquals(1.5.d, 3.5.d - 2.0)
|
||||||
|
assertEquals(7.d, 3.5 * 2.d)
|
||||||
|
assertEquals(7.d, 3.5.d * 2.0)
|
||||||
|
assertEquals(1.75.d, 3.5 / 2.d)
|
||||||
|
assertEquals(1.75.d, 3.5.d / 2.0)
|
||||||
|
assert(1.5 < 2.d)
|
||||||
|
assert(2.5.d > 2.0)
|
||||||
|
assert(2.5 == 2.5.d)
|
||||||
|
assert(2.5.d == 2.5)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMixedRealDecimalNonFiniteResultsStayReal() = runTest {
|
||||||
|
val scope = Script.newScope()
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
import lyng.decimal
|
||||||
|
|
||||||
|
val inf1 = 1.0 / 0.d
|
||||||
|
val inf2 = 1.d / 0.0
|
||||||
|
val inf3 = (1.0 / 0.0) * 2.d
|
||||||
|
val negInf = -1.d / 0.0
|
||||||
|
val nan1 = 0.0 / 0.d
|
||||||
|
val nan2 = (0.0 / 0.0) + 1.d
|
||||||
|
|
||||||
|
assert(inf1 is Real)
|
||||||
|
assert(inf2 is Real)
|
||||||
|
assert(inf3 is Real)
|
||||||
|
assert(negInf is Real)
|
||||||
|
assert(nan1 is Real)
|
||||||
|
assert(nan2 is Real)
|
||||||
|
|
||||||
|
assertEquals("Infinity", inf1.toString())
|
||||||
|
assertEquals("Infinity", inf2.toString())
|
||||||
|
assertEquals("Infinity", inf3.toString())
|
||||||
|
assertEquals("-Infinity", negInf.toString())
|
||||||
|
assertEquals("NaN", nan1.toString())
|
||||||
|
assertEquals("NaN", nan2.toString())
|
||||||
|
|
||||||
|
assert(inf1 > 999999999.d)
|
||||||
|
assert(negInf < -999999999.d)
|
||||||
|
assert(!(nan1 == 1.d))
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDecimalRejectsExplicitNonFiniteRealConversions() = runTest {
|
||||||
|
val scope = Script.newScope()
|
||||||
|
val ex1 = assertFailsWith<ExecutionError> {
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
import lyng.decimal
|
||||||
|
(1.0 / 0.0).d
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val ex2 = assertFailsWith<ExecutionError> {
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
import lyng.decimal
|
||||||
|
Decimal.fromReal(0.0 / 0.0)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"cannot convert non-finite Real to Decimal: Infinity",
|
||||||
|
(ex1.errorObject as ObjException).message.value
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
"cannot convert non-finite Real to Decimal: NaN",
|
||||||
|
(ex2.errorObject as ObjException).message.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testDecimalDivisionUsesDefaultContext() = runTest {
|
fun testDecimalDivisionUsesDefaultContext() = runTest {
|
||||||
val scope = Script.newScope()
|
val scope = Script.newScope()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user