mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-20 09:17:43 +03:00
Daml-LF: make MUL_NUMERIC and DIV_NUMERIC multi-scale (#2921)
* daml-lf: Make MUL_NUMERIC and DIV_NUMERIC multi-scale * update release notes * compiler: fix with type change
This commit is contained in:
parent
f32f1b975e
commit
220a03c9e8
@ -134,15 +134,17 @@ mkAnds [x] = x
|
||||
mkAnds (x:xs) = mkAnd x $ mkAnds xs
|
||||
|
||||
|
||||
alpha, beta :: TypeVarName
|
||||
alpha, beta, gamma :: TypeVarName
|
||||
-- NOTE(MH): We want to avoid shadowing variables in the environment. That's
|
||||
-- what the weird names are for.
|
||||
alpha = TypeVarName "::alpha::"
|
||||
beta = TypeVarName "::beta::"
|
||||
gamma = TypeVarName "::gamma::"
|
||||
|
||||
tAlpha, tBeta :: Type
|
||||
tAlpha, tBeta, tGamma :: Type
|
||||
tAlpha = TVar alpha
|
||||
tBeta = TVar beta
|
||||
tGamma = TVar gamma
|
||||
|
||||
|
||||
infixr 1 :->
|
||||
|
@ -177,8 +177,8 @@ typeOfBuiltin = \case
|
||||
BEGreaterEqNumeric -> pure $ TForall (alpha, KNat) $ TNumeric tAlpha :-> TNumeric tAlpha :-> TBool
|
||||
BEAddNumeric -> pure $ TForall (alpha, KNat) $ TNumeric tAlpha :-> TNumeric tAlpha :-> TNumeric tAlpha
|
||||
BESubNumeric -> pure $ TForall (alpha, KNat) $ TNumeric tAlpha :-> TNumeric tAlpha :-> TNumeric tAlpha
|
||||
BEMulNumeric -> pure $ TForall (alpha, KNat) $ TNumeric tAlpha :-> TNumeric tAlpha :-> TNumeric tAlpha
|
||||
BEDivNumeric -> pure $ TForall (alpha, KNat) $ TNumeric tAlpha :-> TNumeric tAlpha :-> TNumeric tAlpha
|
||||
BEMulNumeric -> pure $ TForall (alpha, KNat) $ TForall (beta, KNat) $ TForall (gamma, KNat) $ TNumeric tAlpha :-> TNumeric tBeta :-> TNumeric tGamma
|
||||
BEDivNumeric -> pure $ TForall (alpha, KNat) $ TForall (beta, KNat) $ TForall (gamma, KNat) $ TNumeric tAlpha :-> TNumeric tBeta :-> TNumeric tGamma
|
||||
BERoundNumeric -> pure $ TForall (alpha, KNat) $ TInt64 :-> TNumeric tAlpha :-> TNumeric tAlpha
|
||||
BEInt64ToNumeric -> pure $ TForall (alpha, KNat) $ TInt64 :-> TNumeric tAlpha
|
||||
BENumericToInt64 -> pure $ TForall (alpha, KNat) $ TNumeric tAlpha :-> TInt64
|
||||
|
@ -164,9 +164,9 @@ convertPrim _ "BEAddDecimal" (TNumeric10 :-> TNumeric10 :-> TNumeric10) =
|
||||
convertPrim _ "BESubDecimal" (TNumeric10 :-> TNumeric10 :-> TNumeric10) =
|
||||
ETyApp (EBuiltin BESubNumeric) (TNat 10)
|
||||
convertPrim _ "BEMulDecimal" (TNumeric10 :-> TNumeric10 :-> TNumeric10) =
|
||||
ETyApp (EBuiltin BEMulNumeric) (TNat 10)
|
||||
ETyApp (ETyApp (ETyApp (EBuiltin BEMulNumeric) (TNat 10)) (TNat 10)) (TNat 10)
|
||||
convertPrim _ "BEDivDecimal" (TNumeric10 :-> TNumeric10 :-> TNumeric10) =
|
||||
ETyApp (EBuiltin BEDivNumeric) (TNat 10)
|
||||
ETyApp (ETyApp (ETyApp (EBuiltin BEDivNumeric) (TNat 10)) (TNat 10)) (TNat 10)
|
||||
convertPrim _ "BERoundDecimal" (TInt64 :-> TNumeric10 :-> TNumeric10) =
|
||||
ETyApp (EBuiltin BERoundNumeric) (TNat 10)
|
||||
convertPrim _ "BEEqual" (TNumeric10 :-> TNumeric10 :-> TBool) =
|
||||
|
@ -14,6 +14,7 @@ import com.digitalasset.daml.lf.language.{LanguageVersion => LV}
|
||||
import com.digitalasset.daml_lf.{DamlLf1 => PLF}
|
||||
import com.google.protobuf.CodedInputStream
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.collection.{breakOut, mutable}
|
||||
|
||||
@ -398,17 +399,11 @@ private[archive] class DecodeV1(minor: LV.Minor) extends Decode.OfPackage[PLF.Pa
|
||||
case PLF.Expr.SumCase.BUILTIN =>
|
||||
val info = DecodeV1.builtinInfoMap(lfExpr.getBuiltin)
|
||||
assertSince(info.minVersion, lfExpr.getBuiltin.getValueDescriptor.getFullName)
|
||||
|
||||
if (info.handleLegacyDecimal) {
|
||||
// FixMe: https://github.com/digital-asset/daml/issues/2289
|
||||
// enable the check once the compiler produces proper DAML-LF 1.dev
|
||||
// info.maxVersion.foreach(assertUntil(_, lfExpr.getBuiltin.getValueDescriptor.getFullName))
|
||||
ETyApp(EBuiltin(info.builtin), TDecimalScale)
|
||||
} else {
|
||||
info.maxVersion.foreach(
|
||||
assertUntil(_, lfExpr.getBuiltin.getValueDescriptor.getFullName))
|
||||
EBuiltin(info.builtin)
|
||||
}
|
||||
info.maxVersion.foreach(assertUntil(_, lfExpr.getBuiltin.getValueDescriptor.getFullName))
|
||||
ntimes[Expr](
|
||||
info.implicitDecimalScaleParameters,
|
||||
ETyApp(_, TDecimalScale),
|
||||
EBuiltin(info.builtin))
|
||||
|
||||
case PLF.Expr.SumCase.REC_CON =>
|
||||
val recCon = lfExpr.getRecCon
|
||||
@ -791,6 +786,10 @@ private[archive] class DecodeV1(minor: LV.Minor) extends Decode.OfPackage[PLF.Pa
|
||||
|
||||
private[lf] object DecodeV1 {
|
||||
|
||||
@tailrec
|
||||
private def ntimes[A](n: Int, f: A => A, a: A): A =
|
||||
if (n == 0) a else ntimes(n - 1, f, f(a))
|
||||
|
||||
case class BuiltinTypeInfo(
|
||||
proto: PLF.PrimType,
|
||||
bTyp: BuiltinType,
|
||||
@ -830,7 +829,7 @@ private[lf] object DecodeV1 {
|
||||
builtin: BuiltinFunction,
|
||||
minVersion: LV = LV.Features.default, // first version that does support the builtin
|
||||
maxVersion: Option[LV] = None, // first version that does not support the builtin
|
||||
handleLegacyDecimal: Boolean = false
|
||||
implicitDecimalScaleParameters: Int = 0
|
||||
)
|
||||
|
||||
val builtinFunctionInfos: List[BuiltinFunctionInfo] = {
|
||||
@ -840,27 +839,32 @@ private[lf] object DecodeV1 {
|
||||
ADD_DECIMAL,
|
||||
BAddNumeric,
|
||||
maxVersion = Some(numeric),
|
||||
handleLegacyDecimal = true),
|
||||
implicitDecimalScaleParameters = 1
|
||||
),
|
||||
BuiltinFunctionInfo(
|
||||
SUB_DECIMAL,
|
||||
BSubNumeric,
|
||||
maxVersion = Some(numeric),
|
||||
handleLegacyDecimal = true),
|
||||
implicitDecimalScaleParameters = 1
|
||||
),
|
||||
BuiltinFunctionInfo(
|
||||
MUL_DECIMAL,
|
||||
BMulNumeric,
|
||||
maxVersion = Some(numeric),
|
||||
handleLegacyDecimal = true),
|
||||
implicitDecimalScaleParameters = 3
|
||||
),
|
||||
BuiltinFunctionInfo(
|
||||
DIV_DECIMAL,
|
||||
BDivNumeric,
|
||||
maxVersion = Some(numeric),
|
||||
handleLegacyDecimal = true),
|
||||
implicitDecimalScaleParameters = 3
|
||||
),
|
||||
BuiltinFunctionInfo(
|
||||
ROUND_DECIMAL,
|
||||
BRoundNumeric,
|
||||
maxVersion = Some(numeric),
|
||||
handleLegacyDecimal = true),
|
||||
implicitDecimalScaleParameters = 1
|
||||
),
|
||||
BuiltinFunctionInfo(ADD_NUMERIC, BAddNumeric, minVersion = numeric),
|
||||
BuiltinFunctionInfo(SUB_NUMERIC, BSubNumeric, minVersion = numeric),
|
||||
BuiltinFunctionInfo(MUL_NUMERIC, BMulNumeric, minVersion = numeric),
|
||||
@ -878,12 +882,14 @@ private[lf] object DecodeV1 {
|
||||
INT64_TO_DECIMAL,
|
||||
BInt64ToNumeric,
|
||||
maxVersion = Some(numeric),
|
||||
handleLegacyDecimal = true),
|
||||
implicitDecimalScaleParameters = 1
|
||||
),
|
||||
BuiltinFunctionInfo(
|
||||
DECIMAL_TO_INT64,
|
||||
BNumericToInt64,
|
||||
maxVersion = Some(numeric),
|
||||
handleLegacyDecimal = true),
|
||||
implicitDecimalScaleParameters = 1
|
||||
),
|
||||
BuiltinFunctionInfo(INT64_TO_NUMERIC, BInt64ToNumeric, minVersion = numeric),
|
||||
BuiltinFunctionInfo(NUMERIC_TO_INT64, BNumericToInt64, minVersion = numeric),
|
||||
BuiltinFunctionInfo(FOLDL, BFoldl),
|
||||
@ -901,7 +907,8 @@ private[lf] object DecodeV1 {
|
||||
LEQ_DECIMAL,
|
||||
BLessEqNumeric,
|
||||
maxVersion = Some(numeric),
|
||||
handleLegacyDecimal = true),
|
||||
implicitDecimalScaleParameters = 1
|
||||
),
|
||||
BuiltinFunctionInfo(LEQ_NUMERIC, BLessEqNumeric, minVersion = numeric),
|
||||
BuiltinFunctionInfo(LEQ_TEXT, BLessEqText),
|
||||
BuiltinFunctionInfo(LEQ_TIMESTAMP, BLessEqTimestamp),
|
||||
@ -911,7 +918,8 @@ private[lf] object DecodeV1 {
|
||||
GEQ_DECIMAL,
|
||||
BGreaterEqNumeric,
|
||||
maxVersion = Some(numeric),
|
||||
handleLegacyDecimal = true),
|
||||
implicitDecimalScaleParameters = 1
|
||||
),
|
||||
BuiltinFunctionInfo(GEQ_NUMERIC, BGreaterEqNumeric, minVersion = numeric),
|
||||
BuiltinFunctionInfo(GEQ_TEXT, BGreaterEqText),
|
||||
BuiltinFunctionInfo(GEQ_TIMESTAMP, BGreaterEqTimestamp),
|
||||
@ -921,7 +929,8 @@ private[lf] object DecodeV1 {
|
||||
LESS_DECIMAL,
|
||||
BLessNumeric,
|
||||
maxVersion = Some(numeric),
|
||||
handleLegacyDecimal = true),
|
||||
implicitDecimalScaleParameters = 1
|
||||
),
|
||||
BuiltinFunctionInfo(LESS_NUMERIC, BLessNumeric, minVersion = numeric),
|
||||
BuiltinFunctionInfo(LESS_TEXT, BLessText),
|
||||
BuiltinFunctionInfo(LESS_TIMESTAMP, BLessTimestamp),
|
||||
@ -931,7 +940,8 @@ private[lf] object DecodeV1 {
|
||||
GREATER_DECIMAL,
|
||||
BGreaterNumeric,
|
||||
maxVersion = Some(numeric),
|
||||
handleLegacyDecimal = true),
|
||||
implicitDecimalScaleParameters = 1
|
||||
),
|
||||
BuiltinFunctionInfo(GREATER_NUMERIC, BGreaterNumeric, minVersion = numeric),
|
||||
BuiltinFunctionInfo(GREATER_TEXT, BGreaterText),
|
||||
BuiltinFunctionInfo(GREATER_TIMESTAMP, BGreaterTimestamp),
|
||||
@ -941,7 +951,8 @@ private[lf] object DecodeV1 {
|
||||
TO_TEXT_DECIMAL,
|
||||
BToTextNumeric,
|
||||
maxVersion = Some(numeric),
|
||||
handleLegacyDecimal = true),
|
||||
implicitDecimalScaleParameters = 1
|
||||
),
|
||||
BuiltinFunctionInfo(TO_TEXT_NUMERIC, BToTextNumeric, minVersion = numeric),
|
||||
BuiltinFunctionInfo(TO_TEXT_TIMESTAMP, BToTextTimestamp),
|
||||
BuiltinFunctionInfo(TO_TEXT_PARTY, BToTextParty, minVersion = partyTextConversions),
|
||||
@ -953,7 +964,7 @@ private[lf] object DecodeV1 {
|
||||
BuiltinFunctionInfo(
|
||||
FROM_TEXT_DECIMAL,
|
||||
BFromTextNumeric,
|
||||
handleLegacyDecimal = true,
|
||||
implicitDecimalScaleParameters = 1,
|
||||
minVersion = numberParsing,
|
||||
maxVersion = Some(numeric)),
|
||||
BuiltinFunctionInfo(FROM_TEXT_NUMERIC, BFromTextNumeric, minVersion = numeric),
|
||||
@ -975,7 +986,8 @@ private[lf] object DecodeV1 {
|
||||
EQUAL_DECIMAL,
|
||||
BEqualNumeric,
|
||||
maxVersion = Some(numeric),
|
||||
handleLegacyDecimal = true),
|
||||
implicitDecimalScaleParameters = 1
|
||||
),
|
||||
BuiltinFunctionInfo(EQUAL_NUMERIC, BEqualNumeric, minVersion = numeric),
|
||||
BuiltinFunctionInfo(EQUAL_TEXT, BEqualText),
|
||||
BuiltinFunctionInfo(EQUAL_TIMESTAMP, BEqualTimestamp),
|
||||
|
@ -7,11 +7,11 @@ import java.math.BigDecimal
|
||||
import java.nio.file.{Files, Paths}
|
||||
|
||||
import com.digitalasset.daml.bazeltools.BazelRunfiles._
|
||||
import com.digitalasset.daml.lf.archive.DecodeV1.BuiltinFunctionInfo
|
||||
import com.digitalasset.daml.lf.archive.Reader.ParseError
|
||||
import com.digitalasset.daml.lf.data.{ImmArray, Ref}
|
||||
import com.digitalasset.daml.lf.language.Util._
|
||||
import com.digitalasset.daml.lf.language.{Ast, LanguageVersion => LV}
|
||||
import com.digitalasset.daml.lf.language.{Ast, LanguageMinorVersion, LanguageVersion => LV}
|
||||
import LanguageMinorVersion.Implicits._
|
||||
import com.digitalasset.daml_lf.DamlLf1
|
||||
import org.scalatest.prop.TableDrivenPropertyChecks
|
||||
import org.scalatest.{Inside, Matchers, OptionValues, WordSpec}
|
||||
@ -217,8 +217,8 @@ class DecodeV1Spec
|
||||
|
||||
"decodeExpr" should {
|
||||
|
||||
def toProto(b: BuiltinFunctionInfo) =
|
||||
DamlLf1.Expr.newBuilder().setBuiltin(b.proto).build()
|
||||
def toProtoExpr(b: DamlLf1.BuiltinFunction) =
|
||||
DamlLf1.Expr.newBuilder().setBuiltin(b).build()
|
||||
|
||||
def toDecimalProto(s: String) =
|
||||
DamlLf1.Expr.newBuilder().setPrimLit(DamlLf1.PrimLit.newBuilder().setDecimal(s)).build()
|
||||
@ -226,39 +226,102 @@ class DecodeV1Spec
|
||||
def toNumericProto(s: String) =
|
||||
DamlLf1.Expr.newBuilder().setPrimLit(DamlLf1.PrimLit.newBuilder().setNumeric(s)).build()
|
||||
|
||||
def toScala(b: BuiltinFunctionInfo) =
|
||||
Ast.EBuiltin(b.builtin)
|
||||
val decimalBuiltinTestCases = Table[DamlLf1.BuiltinFunction, LanguageMinorVersion, Ast.Expr](
|
||||
("decimal builtins", "minVersion", "expected output"),
|
||||
(
|
||||
DamlLf1.BuiltinFunction.ADD_DECIMAL,
|
||||
"1",
|
||||
Ast.ETyApp(Ast.EBuiltin(Ast.BAddNumeric), TDecimalScale)),
|
||||
(
|
||||
DamlLf1.BuiltinFunction.SUB_DECIMAL,
|
||||
"1",
|
||||
Ast.ETyApp(Ast.EBuiltin(Ast.BSubNumeric), TDecimalScale)),
|
||||
(
|
||||
DamlLf1.BuiltinFunction.MUL_DECIMAL,
|
||||
"1",
|
||||
Ast.ETyApp(
|
||||
Ast.ETyApp(Ast.ETyApp(Ast.EBuiltin(Ast.BMulNumeric), TDecimalScale), TDecimalScale),
|
||||
TDecimalScale)),
|
||||
(
|
||||
DamlLf1.BuiltinFunction.DIV_DECIMAL,
|
||||
"1",
|
||||
Ast.ETyApp(
|
||||
Ast.ETyApp(Ast.ETyApp(Ast.EBuiltin(Ast.BDivNumeric), TDecimalScale), TDecimalScale),
|
||||
TDecimalScale)),
|
||||
(
|
||||
DamlLf1.BuiltinFunction.ROUND_DECIMAL,
|
||||
"1",
|
||||
Ast.ETyApp(Ast.EBuiltin(Ast.BRoundNumeric), TDecimalScale)),
|
||||
(
|
||||
DamlLf1.BuiltinFunction.LEQ_DECIMAL,
|
||||
"1",
|
||||
Ast.ETyApp(Ast.EBuiltin(Ast.BLessEqNumeric), TDecimalScale)),
|
||||
(
|
||||
DamlLf1.BuiltinFunction.LESS_DECIMAL,
|
||||
"1",
|
||||
Ast.ETyApp(Ast.EBuiltin(Ast.BLessNumeric), TDecimalScale)),
|
||||
(
|
||||
DamlLf1.BuiltinFunction.GEQ_DECIMAL,
|
||||
"1",
|
||||
Ast.ETyApp(Ast.EBuiltin(Ast.BGreaterEqNumeric), TDecimalScale)),
|
||||
(
|
||||
DamlLf1.BuiltinFunction.GREATER_DECIMAL,
|
||||
"1",
|
||||
Ast.ETyApp(Ast.EBuiltin(Ast.BGreaterNumeric), TDecimalScale)),
|
||||
(
|
||||
DamlLf1.BuiltinFunction.TO_TEXT_DECIMAL,
|
||||
"1",
|
||||
Ast.ETyApp(Ast.EBuiltin(Ast.BToTextNumeric), TDecimalScale)),
|
||||
(
|
||||
DamlLf1.BuiltinFunction.FROM_TEXT_DECIMAL,
|
||||
"5",
|
||||
Ast.ETyApp(Ast.EBuiltin(Ast.BFromTextNumeric), TDecimalScale)),
|
||||
(
|
||||
DamlLf1.BuiltinFunction.INT64_TO_DECIMAL,
|
||||
"1",
|
||||
Ast.ETyApp(Ast.EBuiltin(Ast.BInt64ToNumeric), TDecimalScale)),
|
||||
(
|
||||
DamlLf1.BuiltinFunction.DECIMAL_TO_INT64,
|
||||
"1",
|
||||
Ast.ETyApp(Ast.EBuiltin(Ast.BNumericToInt64), TDecimalScale)),
|
||||
(
|
||||
DamlLf1.BuiltinFunction.EQUAL_DECIMAL,
|
||||
"1",
|
||||
Ast.ETyApp(Ast.EBuiltin(Ast.BEqualNumeric), TDecimalScale)),
|
||||
)
|
||||
|
||||
// All the legacy decimal bultins.
|
||||
val decimalBuilttins =
|
||||
DecodeV1.builtinFunctionInfos.filter(_.handleLegacyDecimal)
|
||||
val numericBuiltinTestCases = Table(
|
||||
"numeric builtins" -> "expected output",
|
||||
DamlLf1.BuiltinFunction.ADD_NUMERIC -> Ast.EBuiltin(Ast.BAddNumeric),
|
||||
DamlLf1.BuiltinFunction.SUB_NUMERIC -> Ast.EBuiltin(Ast.BSubNumeric),
|
||||
DamlLf1.BuiltinFunction.MUL_NUMERIC -> Ast.EBuiltin(Ast.BMulNumeric),
|
||||
DamlLf1.BuiltinFunction.DIV_NUMERIC -> Ast.EBuiltin(Ast.BDivNumeric),
|
||||
DamlLf1.BuiltinFunction.ROUND_NUMERIC -> Ast.EBuiltin(Ast.BRoundNumeric),
|
||||
DamlLf1.BuiltinFunction.LEQ_NUMERIC -> Ast.EBuiltin(Ast.BLessEqNumeric),
|
||||
DamlLf1.BuiltinFunction.LESS_NUMERIC -> Ast.EBuiltin(Ast.BLessNumeric),
|
||||
DamlLf1.BuiltinFunction.GEQ_NUMERIC -> Ast.EBuiltin(Ast.BGreaterEqNumeric),
|
||||
DamlLf1.BuiltinFunction.GREATER_NUMERIC -> Ast.EBuiltin(Ast.BGreaterNumeric),
|
||||
DamlLf1.BuiltinFunction.TO_TEXT_NUMERIC -> Ast.EBuiltin(Ast.BToTextNumeric),
|
||||
DamlLf1.BuiltinFunction.FROM_TEXT_NUMERIC -> Ast.EBuiltin(Ast.BFromTextNumeric),
|
||||
DamlLf1.BuiltinFunction.INT64_TO_NUMERIC -> Ast.EBuiltin(Ast.BInt64ToNumeric),
|
||||
DamlLf1.BuiltinFunction.NUMERIC_TO_INT64 -> Ast.EBuiltin(Ast.BNumericToInt64),
|
||||
DamlLf1.BuiltinFunction.EQUAL_NUMERIC -> Ast.EBuiltin(Ast.BEqualNumeric),
|
||||
)
|
||||
|
||||
// All the numeric versions of the former.
|
||||
val numericBuilttins = {
|
||||
val isNumeric = decimalBuilttins.map(_.builtin).toSet
|
||||
DecodeV1.builtinFunctionInfos.filter(info =>
|
||||
!info.handleLegacyDecimal && isNumeric(info.builtin))
|
||||
}
|
||||
|
||||
// Two other unrelated builtins, no need to test more.
|
||||
val otherBuiltins =
|
||||
DecodeV1.builtinFunctionInfos.filter(
|
||||
info =>
|
||||
info.proto == DamlLf1.BuiltinFunction.ADD_INT64 ||
|
||||
info.proto == DamlLf1.BuiltinFunction.APPEND_TEXT)
|
||||
assert(otherBuiltins.length == 2)
|
||||
|
||||
val decimalBuiltinTestCases = Table("decimal builtinInfo", numericBuilttins: _*)
|
||||
val numericBuiltinTestCases = Table("numeric builtinInfo", numericBuilttins: _*)
|
||||
val negativeBuiltinTestCases = Table("other builtinInfo", otherBuiltins: _*)
|
||||
val negativeBuiltinTestCases = Table(
|
||||
"other builtins" -> "expected output",
|
||||
// We do not need to test all other builtin
|
||||
DamlLf1.BuiltinFunction.ADD_INT64 -> Ast.EBuiltin(Ast.BAddInt64),
|
||||
DamlLf1.BuiltinFunction.APPEND_TEXT -> Ast.EBuiltin(Ast.BAppendText)
|
||||
)
|
||||
|
||||
"translate non numeric/decimal builtin as is for any version" in {
|
||||
val allVersions = Table("all versions", preNumericMinVersions ++ postNumericMinVersions: _*)
|
||||
|
||||
forEvery(allVersions) { version =>
|
||||
val decoder = moduleDecoder(version)
|
||||
forEvery(negativeBuiltinTestCases) { info =>
|
||||
decoder.decodeExpr(toProto(info), "test") shouldBe toScala(info)
|
||||
forEvery(negativeBuiltinTestCases) { (proto, scala) =>
|
||||
decoder.decodeExpr(toProtoExpr(proto), "test") shouldBe scala
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -268,10 +331,9 @@ class DecodeV1Spec
|
||||
forEvery(preNumericMinVersions) { version =>
|
||||
val decoder = moduleDecoder(version)
|
||||
|
||||
forEvery(decimalBuiltinTestCases) { info =>
|
||||
if (LV.ordering.gteq(LV(LV.Major.V1, version), info.minVersion))
|
||||
decoder.decodeExpr(toProto(info), "test") shouldBe Ast
|
||||
.ETyApp(toScala(info), Ast.TNat(10))
|
||||
forEvery(decimalBuiltinTestCases) { (proto, minVersion, scala) =>
|
||||
if (LV.Major.V1.minorVersionOrdering.gteq(version, minVersion))
|
||||
decoder.decodeExpr(toProtoExpr(proto), "test") shouldBe scala
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -281,20 +343,19 @@ class DecodeV1Spec
|
||||
forEvery(preNumericMinVersions) { version =>
|
||||
val decoder = moduleDecoder(version)
|
||||
|
||||
forEvery(numericBuiltinTestCases) { info =>
|
||||
an[ParseError] shouldBe thrownBy(decoder.decodeExpr(toProto(info), "test"))
|
||||
forEvery(numericBuiltinTestCases) { (proto, _) =>
|
||||
an[ParseError] shouldBe thrownBy(decoder.decodeExpr(toProtoExpr(proto), "test"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"translate Numeric builtins as is if version >= 1.dev" in {
|
||||
|
||||
forEvery(preNumericMinVersions) { version =>
|
||||
forEvery(postNumericMinVersions) { version =>
|
||||
val decoder = moduleDecoder(version)
|
||||
|
||||
forEvery(numericBuiltinTestCases) { info =>
|
||||
if (LV.ordering.gteq(LV(LV.Major.V1, version), info.minVersion))
|
||||
decoder.decodeExpr(toProto(info), "test") shouldBe toScala(info)
|
||||
forEvery(numericBuiltinTestCases) { (proto, scala) =>
|
||||
decoder.decodeExpr(toProtoExpr(proto), "test") shouldBe scala
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -303,11 +364,11 @@ class DecodeV1Spec
|
||||
// reactive the test once the decoder is not so lenient
|
||||
"reject Decimal builtins if version >= 1.dev" ignore {
|
||||
|
||||
forEvery(preNumericMinVersions) { version =>
|
||||
forEvery(postNumericMinVersions) { version =>
|
||||
val decoder = moduleDecoder(version)
|
||||
|
||||
forEvery(decimalBuiltinTestCases) { info =>
|
||||
an[ParseError] shouldBe thrownBy(decoder.decodeExpr(toProto(info), "test"))
|
||||
forEvery(decimalBuiltinTestCases) { (proto, _, _) =>
|
||||
an[ParseError] shouldBe thrownBy(decoder.decodeExpr(toProtoExpr(proto), "test"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,29 +98,24 @@ abstract class NumericModule {
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies `x` by `y`. The output has the same scale as the inputs. If rounding must be
|
||||
* Multiplies `x` by `y`. The output has the scale `scale`. If rounding must be
|
||||
* performed, the [[https://en.wikipedia.org/wiki/Rounding#Round_half_to_even> banker's rounding convention]]
|
||||
* is applied.
|
||||
* In case of overflow, returns an error message instead.
|
||||
*
|
||||
* ```Requires the scale of `x` and `y` are the same.```
|
||||
*/
|
||||
final def multiply(x: Numeric, y: Numeric): Either[String, Numeric] = {
|
||||
assert(x.scale == y.scale)
|
||||
checkForOverflow((x multiply y).setScale(x.scale, ROUND_HALF_EVEN))
|
||||
final def multiply(scale: Int, x: Numeric, y: Numeric): Either[String, Numeric] = {
|
||||
checkForOverflow((x multiply y).setScale(scale, ROUND_HALF_EVEN))
|
||||
}
|
||||
|
||||
/**
|
||||
* Divides `x` by `y`. The output has the same scale as the inputs. If rounding must be
|
||||
* Divides `x` by `y`. The output has the scale `scale`. If rounding must be
|
||||
* performed, the [[https://en.wikipedia.org/wiki/Rounding#Round_half_to_even> banker's rounding convention]]
|
||||
* is applied.
|
||||
* In case of overflow, returns an error message instead.
|
||||
*
|
||||
* ```Requires the scale of `x` and `y` are the same.```
|
||||
*/
|
||||
final def divide(x: Numeric, y: Numeric): Either[String, Numeric] = {
|
||||
assert(x.scale == y.scale)
|
||||
checkForOverflow(x.divide(y, x.scale, ROUND_HALF_EVEN))
|
||||
final def divide(scale: Int, x: Numeric, y: Numeric): Either[String, Numeric] = {
|
||||
checkForOverflow(x.divide(y, scale, ROUND_HALF_EVEN))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -193,76 +193,85 @@ class NumericSpec
|
||||
Numeric.assertFromString(s)
|
||||
|
||||
"return an error in case of overflow" in {
|
||||
val testCases = Table[Numeric, Numeric](
|
||||
("input1", "input2"),
|
||||
("10000000000000000000.", "10000000000000000000."),
|
||||
("10000000000000000000.0", "-1000000000000000000.0"),
|
||||
("-1000000000000000000.00", "1000000000000000000.00"),
|
||||
("-100000000000000000.000", "-1000000000000000000.000"),
|
||||
("10.000000000000000000000000000000000000", "10.000000000000000000000000000000000000"),
|
||||
("5678901234567890.12345678901234", "-5678901234567890.12345678901234"),
|
||||
val testCases = Table[Int, Numeric, Numeric](
|
||||
("scale", "input1", "input2"),
|
||||
(0, "10000000000000000000.", "10000000000000000000."),
|
||||
(1, "10000000000000000000.0", "-1000000000000000000.0"),
|
||||
(2, "-1000000000000000000.00", "1000000000000000000.00"),
|
||||
(3, "-100000000000000000.000", "-1000000000000000000.000"),
|
||||
(36, "10.000000000000000000000000000000000000", "10.000000000000000000000000000000000000"),
|
||||
(14, "5678901234567890.12345678901234", "-5678901234567890.12345678901234"),
|
||||
)
|
||||
|
||||
multiply("10000000000000000000.", "1000000000000000000.") shouldBe 'right
|
||||
multiply(0, "10000000000000000000.", "1000000000000000000.") shouldBe 'right
|
||||
|
||||
forEvery(testCases) { (x, y) =>
|
||||
multiply(x, y) shouldBe 'left
|
||||
forEvery(testCases) { (scale, x, y) =>
|
||||
multiply(scale, x, y) shouldBe 'left
|
||||
}
|
||||
}
|
||||
|
||||
"multiply two numeric properly" in {
|
||||
val testCases = Table[Numeric, Numeric, Numeric](
|
||||
("input1", "input2", "result"),
|
||||
("0.00000", "0.00000", "0.00000"),
|
||||
val testCases = Table[Int, Numeric, Numeric, Numeric](
|
||||
("scale", "input1", "input2", "result"),
|
||||
(5, "0.00000", "0.00000", "0.00000"),
|
||||
(
|
||||
38,
|
||||
"0.00000000000000000010000000000000000000",
|
||||
"0.00000000000000000010000000000000000000",
|
||||
"0.00000000000000000000000000000000000001"
|
||||
),
|
||||
(
|
||||
37,
|
||||
"1.0000000000000000000000000000000000000",
|
||||
"-0.0000000000000000000000000000000000001",
|
||||
"-0.0000000000000000000000000000000000001"
|
||||
),
|
||||
(
|
||||
18,
|
||||
"-1000000000000000000.000000000000000000",
|
||||
"0.000000000000000001",
|
||||
"-1.000000000000000000"
|
||||
),
|
||||
(
|
||||
37,
|
||||
"3.1415926535897932384626433832795028842",
|
||||
"2.7182818284590452353602874713526624978",
|
||||
"8.5397342226735670654635508695465744952"
|
||||
),
|
||||
(
|
||||
1,
|
||||
"0.5",
|
||||
"0.1",
|
||||
"0.0",
|
||||
),
|
||||
(
|
||||
2,
|
||||
"0.15",
|
||||
"0.10",
|
||||
"0.02",
|
||||
),
|
||||
(
|
||||
3,
|
||||
"1.006",
|
||||
"0.100",
|
||||
"0.101"
|
||||
),
|
||||
(
|
||||
4,
|
||||
"2.1003",
|
||||
"0.1000",
|
||||
"0.2100"
|
||||
),
|
||||
(
|
||||
0,
|
||||
"5555555555555555555.",
|
||||
"4444444444444444444.",
|
||||
"24691358024691358019753086419753086420."
|
||||
)
|
||||
)
|
||||
|
||||
forEvery(testCases) { (x, y, z) =>
|
||||
multiply(x, y) shouldBe Right(z)
|
||||
forEvery(testCases) { (scale, x, y, z) =>
|
||||
multiply(scale, x, y) shouldBe Right(z)
|
||||
}
|
||||
|
||||
}
|
||||
@ -274,77 +283,82 @@ class NumericSpec
|
||||
implicit def toNumeric(s: String): Numeric = Numeric.assertFromString(s)
|
||||
|
||||
"return an error in case of overflow" in {
|
||||
val testCases = Table[Numeric, Numeric](
|
||||
("input1", "input2"),
|
||||
("1000000000000000000.0000000000", "0.0000000001"),
|
||||
("-1000000000000000000000000000000000000.0", "0.1"),
|
||||
val testCases = Table[Int, Numeric, Numeric](
|
||||
("scale", "input1", "input2"),
|
||||
(10, "1000000000000000000.0000000000", "0.0000000001"),
|
||||
(1, "-1000000000000000000000000000000000000.0", "0.1"),
|
||||
(
|
||||
38,
|
||||
"0.10000000000000000000000000000000000000",
|
||||
"-0.10000000000000000000000000000000000000",
|
||||
),
|
||||
("5678901234567890.12345678901234", "-0.00000000001234"),
|
||||
(14, "5678901234567890.12345678901234", "-0.00000000001234"),
|
||||
)
|
||||
|
||||
divide("100000000000000000.0000000000", "0.0000000001") shouldBe 'right
|
||||
divide(10, "100000000000000000.0000000000", "0.0000000001") shouldBe 'right
|
||||
|
||||
forEvery(testCases) { (x, y) =>
|
||||
divide(x, y) shouldBe 'left
|
||||
forEvery(testCases) { (scale, x, y) =>
|
||||
divide(scale, x, y) shouldBe 'left
|
||||
}
|
||||
}
|
||||
|
||||
"divide two numerics properly" in {
|
||||
val testCases = Table[Numeric, Numeric, Numeric](
|
||||
("input1", "input2", "result"),
|
||||
("0.00000", "1.00000", "0.00000"),
|
||||
val testCases = Table[Int, Numeric, Numeric, Numeric](
|
||||
("scale", "input1", "input2", "result"),
|
||||
(5, "0.00000", "1.00000", "0.00000"),
|
||||
(
|
||||
38,
|
||||
"0.00000000000000000010000000000000000000",
|
||||
"-0.00000000000000000100000000000000000000",
|
||||
"-0.10000000000000000000000000000000000000"
|
||||
),
|
||||
(
|
||||
37,
|
||||
"0.0000000000000000000000000000000000001",
|
||||
"-0.1000000000000000000000000000000000001",
|
||||
"-0.0000000000000000000000000000000000010"
|
||||
),
|
||||
(
|
||||
18,
|
||||
"1.000000000000000000",
|
||||
"-0.000000000000000001",
|
||||
"-1000000000000000000.000000000000000000",
|
||||
),
|
||||
(
|
||||
37,
|
||||
"3.1415926535897932384626433832795028842",
|
||||
"2.7182818284590452353602874713526624978",
|
||||
"1.1557273497909217179100931833126962991"
|
||||
),
|
||||
(
|
||||
1,
|
||||
"1.0",
|
||||
"4.0",
|
||||
"0.2",
|
||||
),
|
||||
(1, "6.0", "8.0", "0.8"),
|
||||
(
|
||||
"6.0",
|
||||
"8.0",
|
||||
"0.8",
|
||||
),
|
||||
(
|
||||
3,
|
||||
"1.006",
|
||||
"10.000",
|
||||
"0.101"
|
||||
),
|
||||
(
|
||||
4,
|
||||
"2.1003",
|
||||
"10.0000",
|
||||
"0.2100"
|
||||
),
|
||||
(
|
||||
19,
|
||||
"5555555555555555555.5555555555555555555",
|
||||
"4343434343434343434.4343434343434343434",
|
||||
"1.2790697674418604651"
|
||||
)
|
||||
)
|
||||
|
||||
forEvery(testCases) { (x, y, z) =>
|
||||
divide(x, y) shouldBe Right(z)
|
||||
forEvery(testCases) { (scale, x, y, z) =>
|
||||
divide(scale, x, y) shouldBe Right(z)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,8 +157,7 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {
|
||||
case TApp(fun, arg) => fun -> arg
|
||||
})
|
||||
|
||||
private def ignoreFirstTNatForDecimalLegacy(typs: ImmArray[Type]): ImmArray[Type] =
|
||||
// BTNumeric must be applied to a TNat that we should ignore
|
||||
private def ignoreOneDecimalScaleParameter(typs: ImmArray[Type]): ImmArray[Type] =
|
||||
typs match {
|
||||
case ImmArrayCons(TNat(_), tail) => tail
|
||||
case _ =>
|
||||
@ -196,7 +195,7 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {
|
||||
case TBuiltin(bType) =>
|
||||
val (proto, typs) =
|
||||
if (bType == BTNumeric && versionIsOlderThan(LV.Features.numeric))
|
||||
PLF.PrimType.DECIMAL -> ignoreFirstTNatForDecimalLegacy(args)
|
||||
PLF.PrimType.DECIMAL -> ignoreOneDecimalScaleParameter(args)
|
||||
else
|
||||
builtinTypeInfoMap(bType).proto -> args
|
||||
builder.setPrim(
|
||||
@ -405,10 +404,10 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {
|
||||
case ETyAbs(binder, body) => binder -> body
|
||||
})
|
||||
|
||||
private def isLegacyDecimalBuiltin(expr: Expr) =
|
||||
private def implicitDecimalScaleParameters(expr: Expr) =
|
||||
expr match {
|
||||
case EBuiltin(f) => builtinFunctionMap(f).handleLegacyDecimal
|
||||
case _ => false
|
||||
case EBuiltin(f) => builtinFunctionMap(f).implicitDecimalScaleParameters
|
||||
case _ => 0
|
||||
}
|
||||
|
||||
private def encodeExprBuilder(expr0: Expr): PLF.Expr.Builder = {
|
||||
@ -460,11 +459,8 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {
|
||||
case EApps(fun, args) =>
|
||||
newBuilder.setApp(PLF.Expr.App.newBuilder().setFun(fun).accumulateLeft(args)(_ addArgs _))
|
||||
case ETyApps(expr, typs1) =>
|
||||
val typs: ImmArray[Type] =
|
||||
if (isLegacyDecimalBuiltin(expr) && versionIsOlderThan(LV.Features.numeric))
|
||||
ignoreFirstTNatForDecimalLegacy(typs1)
|
||||
else
|
||||
typs1
|
||||
val typs =
|
||||
ntimes(implicitDecimalScaleParameters(expr), ignoreOneDecimalScaleParameter, typs1)
|
||||
newBuilder.setTyApp(
|
||||
PLF.Expr.TyApp.newBuilder().setExpr(expr).accumulateLeft(typs)(_ addTypes _))
|
||||
case ETyApps(expr, typs) =>
|
||||
@ -602,6 +598,10 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {
|
||||
|
||||
object EncodeV1 {
|
||||
|
||||
@tailrec
|
||||
private def ntimes[A](n: Int, f: A => A, a: A): A =
|
||||
if (n == 0) a else ntimes(n - 1, f, f(a))
|
||||
|
||||
private sealed abstract class LeftRecMatcher[Left, Right] {
|
||||
def unapply(arg: Left): Option[(Left, ImmArray[Right])]
|
||||
}
|
||||
|
@ -134,28 +134,28 @@ object SBuiltin {
|
||||
Numeric.add(x, y)
|
||||
)
|
||||
|
||||
private def divide(x: Numeric, y: Numeric): Numeric =
|
||||
if (y.signum() == 0)
|
||||
throw DamlEArithmeticError(
|
||||
s"Attempt to divide ${Numeric.toString(x)} by ${Numeric.toString(y)}.")
|
||||
else
|
||||
rightOrArithmeticError(
|
||||
s"(Numeric ${x.scale}) overflow when dividing ${Numeric.toString(x)} by ${Numeric.toString(y)}.",
|
||||
Numeric.divide(x, y)
|
||||
)
|
||||
|
||||
private def multiply(x: Numeric, y: Numeric): Numeric =
|
||||
rightOrArithmeticError(
|
||||
s"(Numeric ${x.scale}) overflow when multiplying ${Numeric.toString(x)} by ${Numeric.toString(y)}.",
|
||||
Numeric.multiply(x, y)
|
||||
)
|
||||
|
||||
private def subtract(x: Numeric, y: Numeric): Numeric =
|
||||
rightOrArithmeticError(
|
||||
s"(Numeric ${x.scale}) overflow when subtracting ${Numeric.toString(y)} from ${Numeric.toString(x)}.",
|
||||
Numeric.subtract(x, y)
|
||||
)
|
||||
|
||||
private def multiply(scale: Int, x: Numeric, y: Numeric): Numeric =
|
||||
rightOrArithmeticError(
|
||||
s"(Numeric ${scale}) overflow when multiplying ${Numeric.toString(x)} by ${Numeric.toString(y)}.",
|
||||
Numeric.multiply(scale, x, y)
|
||||
)
|
||||
|
||||
private def divide(scale: Int, x: Numeric, y: Numeric): Numeric =
|
||||
if (y.signum() == 0)
|
||||
throw DamlEArithmeticError(
|
||||
s"Attempt to divide ${Numeric.toString(x)} by ${Numeric.toString(y)}.")
|
||||
else
|
||||
rightOrArithmeticError(
|
||||
s"(Numeric ${scale}) overflow when dividing ${Numeric.toString(x)} by ${Numeric.toString(y)}.",
|
||||
Numeric.divide(scale, x, y)
|
||||
)
|
||||
|
||||
sealed abstract class SBBinaryOpNumeric(op: (Numeric, Numeric) => Numeric) extends SBuiltin(3) {
|
||||
final def execute(args: util.ArrayList[SValue], machine: Machine): Unit = {
|
||||
val scale = args.get(0).asInstanceOf[STNat].n
|
||||
@ -166,10 +166,23 @@ object SBuiltin {
|
||||
}
|
||||
}
|
||||
|
||||
sealed abstract class SBBinaryOpNumeric2(op: (Int, Numeric, Numeric) => Numeric)
|
||||
extends SBuiltin(5) {
|
||||
final def execute(args: util.ArrayList[SValue], machine: Machine): Unit = {
|
||||
val scaleA = args.get(0).asInstanceOf[STNat].n
|
||||
val scaleB = args.get(1).asInstanceOf[STNat].n
|
||||
val scale = args.get(2).asInstanceOf[STNat].n
|
||||
val a = args.get(3).asInstanceOf[SNumeric].value
|
||||
val b = args.get(4).asInstanceOf[SNumeric].value
|
||||
assert(a.scale == scaleA && b.scale == scaleB)
|
||||
machine.ctrl = CtrlValue(SNumeric(op(scale, a, b)))
|
||||
}
|
||||
}
|
||||
|
||||
final case object SBAddNumeric extends SBBinaryOpNumeric(add)
|
||||
final case object SBSubNumeric extends SBBinaryOpNumeric(subtract)
|
||||
final case object SBMulNumeric extends SBBinaryOpNumeric(multiply)
|
||||
final case object SBDivNumeric extends SBBinaryOpNumeric(divide)
|
||||
final case object SBMulNumeric extends SBBinaryOpNumeric2(multiply)
|
||||
final case object SBDivNumeric extends SBBinaryOpNumeric2(divide)
|
||||
|
||||
final case object SBRoundNumeric extends SBuiltin(3) {
|
||||
def execute(args: util.ArrayList[SValue], machine: Machine): Unit = {
|
||||
|
@ -263,15 +263,16 @@ class SBuiltinTest extends FreeSpec with Matchers with TableDrivenPropertyChecks
|
||||
val overSqrtOfTen = "3.1622776601683793319988935444327185338"
|
||||
|
||||
"throws exception in case of overflow" in {
|
||||
eval(e"$builtin @0 1${"0" * 18}. 1${"0" * 19}.") shouldBe 'right
|
||||
eval(e"$builtin @0 1${"0" * 19}. 1${"0" * 19}.") shouldBe 'left
|
||||
eval(e"$builtin @37 $underSqrtOfTen $underSqrtOfTen") shouldBe 'right
|
||||
eval(e"$builtin @37 $overSqrtOfTen $underSqrtOfTen") shouldBe 'left
|
||||
eval(e"$builtin @10 1.1000000000 2.2000000000") shouldBe Right(SNumeric(n(10, 2.42)))
|
||||
eval(e"$builtin @10 ${tenPowerOf(13)} ${tenPowerOf(14)}") shouldBe Right(
|
||||
eval(e"$builtin @0 @0 @0 1${"0" * 18}. 1${"0" * 19}.") shouldBe 'right
|
||||
eval(e"$builtin @0 @0 @0 1${"0" * 19}. 1${"0" * 19}.") shouldBe 'left
|
||||
eval(e"$builtin @37 @37 @37 $underSqrtOfTen $underSqrtOfTen") shouldBe 'right
|
||||
eval(e"$builtin @37 @37 @37 $overSqrtOfTen $underSqrtOfTen") shouldBe 'left
|
||||
eval(e"$builtin @10 @10 @10 1.1000000000 2.2000000000") shouldBe Right(
|
||||
SNumeric(n(10, 2.42)))
|
||||
eval(e"$builtin @10 @10 @10 ${tenPowerOf(13)} ${tenPowerOf(14)}") shouldBe Right(
|
||||
SNumeric(n(10, "1E27")))
|
||||
eval(e"$builtin @10 ${tenPowerOf(14)} ${tenPowerOf(14)}") shouldBe 'left
|
||||
eval(e"$builtin @10 ${s(10, bigBigDecimal)} ${bigBigDecimal - 1}") shouldBe Left(
|
||||
eval(e"$builtin @10 @10 @10 ${tenPowerOf(14)} ${tenPowerOf(14)}") shouldBe 'left
|
||||
eval(e"$builtin @10 @10 @10 ${s(10, bigBigDecimal)} ${bigBigDecimal - 1}") shouldBe Left(
|
||||
DamlEArithmeticError(
|
||||
s"(Numeric 10) overflow when multiplying ${s(10, bigBigDecimal)} by ${s(10, bigBigDecimal - 1)}.")
|
||||
)
|
||||
@ -281,25 +282,25 @@ class SBuiltinTest extends FreeSpec with Matchers with TableDrivenPropertyChecks
|
||||
"DIV_NUMERIC" - {
|
||||
val builtin = "DIV_NUMERIC"
|
||||
"throws exception in case of overflow" in {
|
||||
eval(e"$builtin @38 ${s(38, "1E-18")} ${s(38, "-1E-17")}") shouldBe 'right
|
||||
eval(e"$builtin @38 ${s(38, "1E-18")} ${s(38, "-1E-18")}") shouldBe 'left
|
||||
eval(e"$builtin @1 ${s(1, "1E36")} 0.2") shouldBe 'right
|
||||
eval(e"$builtin @1 ${s(1, "1E36")} 0.1") shouldBe 'left
|
||||
eval(e"$builtin @10 1.1000000000 2.2000000000") shouldBe Right(SNumeric(n(10, 0.5)))
|
||||
eval(e"$builtin @10 ${s(10, bigBigDecimal)} ${tenPowerOf(-10)}") shouldBe 'left
|
||||
eval(e"$builtin @10 ${tenPowerOf(17)} ${tenPowerOf(-10)}") shouldBe Right(
|
||||
eval(e"$builtin @38 @38 @38 ${s(38, "1E-18")} ${s(38, "-1E-17")}") shouldBe 'right
|
||||
eval(e"$builtin @38 @38 @38 ${s(38, "1E-18")} ${s(38, "-1E-18")}") shouldBe 'left
|
||||
eval(e"$builtin @1 @1 @1 ${s(1, "1E36")} 0.2") shouldBe 'right
|
||||
eval(e"$builtin @1 @1 @1 ${s(1, "1E36")} 0.1") shouldBe 'left
|
||||
eval(e"$builtin @10 @10 @10 1.1000000000 2.2000000000") shouldBe Right(SNumeric(n(10, 0.5)))
|
||||
eval(e"$builtin @10 @10 @10 ${s(10, bigBigDecimal)} ${tenPowerOf(-10)}") shouldBe 'left
|
||||
eval(e"$builtin @10 @10 @10 ${tenPowerOf(17)} ${tenPowerOf(-10)}") shouldBe Right(
|
||||
SNumeric(n(10, "1E27")))
|
||||
eval(e"$builtin @10 ${tenPowerOf(18)} ${tenPowerOf(-10)}") shouldBe Left(
|
||||
eval(e"$builtin @10 @10 @10 ${tenPowerOf(18)} ${tenPowerOf(-10)}") shouldBe Left(
|
||||
DamlEArithmeticError(
|
||||
s"(Numeric 10) overflow when dividing ${tenPowerOf(18)} by ${tenPowerOf(-10)}.")
|
||||
)
|
||||
}
|
||||
|
||||
"throws exception when divided by 0" in {
|
||||
eval(e"$builtin @10 ${s(10, one)} ${tenPowerOf(-10)}") shouldBe Right(
|
||||
eval(e"$builtin @10 @10 @10 ${s(10, one)} ${tenPowerOf(-10)}") shouldBe Right(
|
||||
SNumeric(n(10, tenPowerOf(10))))
|
||||
eval(e"$builtin @10 ${s(10, one)} ${s(10, zero)}") shouldBe 'left
|
||||
eval(e"$builtin @10 ${s(10, bigBigDecimal)} ${s(10, zero)}") shouldBe Left(
|
||||
eval(e"$builtin @10 @10 @10 ${s(10, one)} ${s(10, zero)}") shouldBe 'left
|
||||
eval(e"$builtin @10 @10 @10 ${s(10, bigBigDecimal)} ${s(10, zero)}") shouldBe Left(
|
||||
DamlEArithmeticError(s"Attempt to divide ${s(10, bigBigDecimal)} by 0.0000000000.")
|
||||
)
|
||||
|
||||
@ -340,23 +341,23 @@ class SBuiltinTest extends FreeSpec with Matchers with TableDrivenPropertyChecks
|
||||
|
||||
val testCases = Table[String, (Numeric, Numeric) => Either[Any, SValue]](
|
||||
("builtin", "reference"),
|
||||
("ADD_NUMERIC", (a, b) => Right(SNumeric(n(10, a add b)))),
|
||||
("SUB_NUMERIC", (a, b) => Right(SNumeric(n(10, a subtract b)))),
|
||||
("MUL_NUMERIC", (a, b) => Right(SNumeric(round(a multiply b)))),
|
||||
("ADD_NUMERIC @10", (a, b) => Right(SNumeric(n(10, a add b)))),
|
||||
("SUB_NUMERIC @10", (a, b) => Right(SNumeric(n(10, a subtract b)))),
|
||||
("MUL_NUMERIC @10 @10 @10 ", (a, b) => Right(SNumeric(round(a multiply b)))),
|
||||
(
|
||||
"DIV_NUMERIC",
|
||||
"DIV_NUMERIC @10 @10 @10",
|
||||
(a, b) => Either.cond(b.signum != 0, SNumeric(round(BigDecimal(a) / BigDecimal(b))), ())),
|
||||
("LESS_EQ_NUMERIC", (a, b) => Right(SBool(BigDecimal(a) <= BigDecimal(b)))),
|
||||
("GREATER_EQ_NUMERIC", (a, b) => Right(SBool(BigDecimal(a) >= BigDecimal(b)))),
|
||||
("LESS_NUMERIC", (a, b) => Right(SBool(BigDecimal(a) < BigDecimal(b)))),
|
||||
("GREATER_NUMERIC", (a, b) => Right(SBool(BigDecimal(a) > BigDecimal(b)))),
|
||||
("EQUAL_NUMERIC", (a, b) => Right(SBool(BigDecimal(a) == BigDecimal(b)))),
|
||||
("LESS_EQ_NUMERIC @10", (a, b) => Right(SBool(BigDecimal(a) <= BigDecimal(b)))),
|
||||
("GREATER_EQ_NUMERIC @10", (a, b) => Right(SBool(BigDecimal(a) >= BigDecimal(b)))),
|
||||
("LESS_NUMERIC @10", (a, b) => Right(SBool(BigDecimal(a) < BigDecimal(b)))),
|
||||
("GREATER_NUMERIC @10", (a, b) => Right(SBool(BigDecimal(a) > BigDecimal(b)))),
|
||||
("EQUAL_NUMERIC @10", (a, b) => Right(SBool(BigDecimal(a) == BigDecimal(b)))),
|
||||
)
|
||||
|
||||
forEvery(testCases) { (builtin, ref) =>
|
||||
forEvery(decimals) { a =>
|
||||
forEvery(decimals) { b =>
|
||||
eval(e"$builtin @10 ${s(10, a)} ${s(10, b)}").left
|
||||
eval(e"$builtin ${s(10, a)} ${s(10, b)}").left
|
||||
.map(_ => ()) shouldBe ref(n(10, a), n(10, b))
|
||||
}
|
||||
}
|
||||
|
@ -314,8 +314,8 @@ object Ast {
|
||||
// Numeric arithmetic
|
||||
final case object BAddNumeric extends BuiltinFunction(2) // : ∀s. Numeric s → Numeric s → Numeric s
|
||||
final case object BSubNumeric extends BuiltinFunction(2) // : ∀s. Numeric s → Numeric s → Numeric s
|
||||
final case object BMulNumeric extends BuiltinFunction(2) // : ∀s. Numeric s → Numeric s → Numeric s
|
||||
final case object BDivNumeric extends BuiltinFunction(2) // : ∀s. Numeric s → Numeric s → Numeric s
|
||||
final case object BMulNumeric extends BuiltinFunction(2) // : ∀s1 s2 s. Numeric s1 → Numeric s2 → Numeric s
|
||||
final case object BDivNumeric extends BuiltinFunction(2) // : ∀s1 s2 s. Numeric s1 → Numeric s2 → Numeric s
|
||||
final case object BRoundNumeric extends BuiltinFunction(2) // : ∀s. Integer → Numeric s → Numeric s
|
||||
final case object BCastNumeric extends BuiltinFunction(1) // : ∀s1 s2. Numeric s1 → Numeric s2
|
||||
final case object BShiftNumeric extends BuiltinFunction(1) // : ∀s1 s2. Numeric s1 → Numeric s2
|
||||
|
@ -2156,33 +2156,23 @@ Numeric functions
|
||||
scale of the inputs and the output is given by the type parameter
|
||||
`α`. Throws an error if overflow.
|
||||
|
||||
* ``MUL_NUMERIC : ∀ (α : nat) . 'Numeric' α → 'Numeric' α → 'Numeric' α``
|
||||
* ``MUL_NUMERIC : ∀ (α₁ α₂ α : nat) . 'Numeric' α₁ → 'Numeric' α₂ → 'Numeric' α``
|
||||
|
||||
Multiplies the two decimals and rounds the result to the closest
|
||||
Multiplies the two numerics and rounds the result to the closest
|
||||
multiple of ``10⁻ᵅ`` using `banker's rounding convention
|
||||
<https://en.wikipedia.org/wiki/Rounding#Round_half_to_even>`_. The
|
||||
scale of the inputs and the output is given by the type parameter
|
||||
`α`. Throws an error in case of overflow.
|
||||
<https://en.wikipedia.org/wiki/Rounding#Round_half_to_even>`_.
|
||||
The type parameters `α₁`, `α₂`, `α` define the scale of the first
|
||||
input, the second input, and the output, respectively. Throws an
|
||||
error in case of overflow.
|
||||
|
||||
* ``DIV_NUMERIC : ∀ (α : nat) . 'Numeric' α → 'Numeric' α → 'Numeric' α``
|
||||
* ``DIV_NUMERIC : ∀ (α₁ α₂ α : nat) . 'Numeric' α₁ → 'Numeric' α₂ → 'Numeric' α``
|
||||
|
||||
Divides the first decimal by the second one and rounds the result to
|
||||
the closest multiple of ``10⁻ᵅ`` using `banker's rounding convention
|
||||
<https://en.wikipedia.org/wiki/Rounding#Round_half_to_even>`_ (where
|
||||
`n` is given as the type parameter). The scale of the inputs and
|
||||
the output is given by the type parameter `α`. Throws an error in
|
||||
case of overflow.
|
||||
|
||||
* ``ROUND_NUMERIC : ∀ (α : nat) . 'Int64' → 'Numeric' α → 'Numeric' α``
|
||||
|
||||
Rounds the decimal to the closest multiple of ``10ⁱ`` where ``i`` is
|
||||
integer argument. In case the value to be rounded is exactly
|
||||
half-way between two multiples, rounds toward the even one,
|
||||
following the `banker's rounding convention
|
||||
<https://en.wikipedia.org/wiki/Rounding#Round_half_to_even>`_. The
|
||||
scale of the inputs and the output is given by the type parameter
|
||||
`α`. Throws an exception if the integer is not between `α-37` and
|
||||
`α` inclusive.
|
||||
`n` is given as the type parameter). The type parameters `α₁`,
|
||||
`α₂`, `α` define the scale of the first input, the second input, and
|
||||
the output, respectively. Throws an error in case of overflow.
|
||||
|
||||
|
||||
* ``CAST_NUMERIC : ∀ (α₁, α₂: nat) . 'Numeric' α₁ → 'Numeric' α₂``
|
||||
|
@ -42,8 +42,15 @@ private[validation] object Typing {
|
||||
protected[validation] lazy val typeOfBuiltinFunction = {
|
||||
val alpha = TVar(Name.assertFromString("$alpha$"))
|
||||
val beta = TVar(Name.assertFromString("$beta$"))
|
||||
val gamma = TVar(Name.assertFromString("$beta$"))
|
||||
def tBinop(typ: Type): Type = typ ->: typ ->: typ
|
||||
val tNumBinop = TForall(alpha.name -> KNat, tBinop(TNumeric(alpha)))
|
||||
val tMultiNumBinop =
|
||||
TForall(
|
||||
alpha.name -> KNat,
|
||||
TForall(
|
||||
beta.name -> KNat,
|
||||
TForall(gamma.name -> KNat, TNumeric(alpha) ->: TNumeric(beta) ->: TNumeric(gamma))))
|
||||
val tNumConversion =
|
||||
TForall(alpha.name -> KNat, TForall(beta.name -> KNat, TNumeric(alpha) ->: TNumeric(beta)))
|
||||
def tComparison(bType: BuiltinType): Type = TBuiltin(bType) ->: TBuiltin(bType) ->: TBool
|
||||
@ -54,8 +61,8 @@ private[validation] object Typing {
|
||||
// Numeric arithmetic
|
||||
BAddNumeric -> tNumBinop,
|
||||
BSubNumeric -> tNumBinop,
|
||||
BMulNumeric -> tNumBinop,
|
||||
BDivNumeric -> tNumBinop,
|
||||
BMulNumeric -> tMultiNumBinop,
|
||||
BDivNumeric -> tMultiNumBinop,
|
||||
BRoundNumeric -> TForall(alpha.name -> KNat, TInt64 ->: TNumeric(alpha) ->: TNumeric(alpha)),
|
||||
BCastNumeric -> tNumConversion,
|
||||
BShiftNumeric -> tNumConversion,
|
||||
|
@ -12,3 +12,4 @@ HEAD — ongoing
|
||||
* [DAML Docs] suppress instance documentation when `--data-only` mode is requested
|
||||
+ [DAML-LF] add CAST_NUMERIC and SHIFT_NUMERIC in DAML-LF 1.dev
|
||||
+ [Sandbox] Dramatically increased performance of the ActiveContractService by only loading the contracts that the parties in the transaction filter are allowed to see.
|
||||
+ [DAML-LF] change signature of MUL_NUMERIC and DIV_NUMERIC
|
||||
|
Loading…
Reference in New Issue
Block a user