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:
Remy 2019-09-17 16:32:49 +02:00 committed by mergify[bot]
parent f32f1b975e
commit 220a03c9e8
14 changed files with 297 additions and 201 deletions

View File

@ -134,15 +134,17 @@ mkAnds [x] = x
mkAnds (x:xs) = mkAnd x $ mkAnds xs 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 -- NOTE(MH): We want to avoid shadowing variables in the environment. That's
-- what the weird names are for. -- what the weird names are for.
alpha = TypeVarName "::alpha::" alpha = TypeVarName "::alpha::"
beta = TypeVarName "::beta::" beta = TypeVarName "::beta::"
gamma = TypeVarName "::gamma::"
tAlpha, tBeta :: Type tAlpha, tBeta, tGamma :: Type
tAlpha = TVar alpha tAlpha = TVar alpha
tBeta = TVar beta tBeta = TVar beta
tGamma = TVar gamma
infixr 1 :-> infixr 1 :->

View File

@ -177,8 +177,8 @@ typeOfBuiltin = \case
BEGreaterEqNumeric -> pure $ TForall (alpha, KNat) $ TNumeric tAlpha :-> TNumeric tAlpha :-> TBool BEGreaterEqNumeric -> pure $ TForall (alpha, KNat) $ TNumeric tAlpha :-> TNumeric tAlpha :-> TBool
BEAddNumeric -> pure $ TForall (alpha, KNat) $ TNumeric tAlpha :-> TNumeric tAlpha :-> TNumeric tAlpha BEAddNumeric -> pure $ TForall (alpha, KNat) $ TNumeric tAlpha :-> TNumeric tAlpha :-> TNumeric tAlpha
BESubNumeric -> 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 BEMulNumeric -> pure $ TForall (alpha, KNat) $ TForall (beta, KNat) $ TForall (gamma, KNat) $ TNumeric tAlpha :-> TNumeric tBeta :-> TNumeric tGamma
BEDivNumeric -> pure $ TForall (alpha, KNat) $ TNumeric tAlpha :-> TNumeric tAlpha :-> TNumeric tAlpha 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 BERoundNumeric -> pure $ TForall (alpha, KNat) $ TInt64 :-> TNumeric tAlpha :-> TNumeric tAlpha
BEInt64ToNumeric -> pure $ TForall (alpha, KNat) $ TInt64 :-> TNumeric tAlpha BEInt64ToNumeric -> pure $ TForall (alpha, KNat) $ TInt64 :-> TNumeric tAlpha
BENumericToInt64 -> pure $ TForall (alpha, KNat) $ TNumeric tAlpha :-> TInt64 BENumericToInt64 -> pure $ TForall (alpha, KNat) $ TNumeric tAlpha :-> TInt64

View File

@ -164,9 +164,9 @@ convertPrim _ "BEAddDecimal" (TNumeric10 :-> TNumeric10 :-> TNumeric10) =
convertPrim _ "BESubDecimal" (TNumeric10 :-> TNumeric10 :-> TNumeric10) = convertPrim _ "BESubDecimal" (TNumeric10 :-> TNumeric10 :-> TNumeric10) =
ETyApp (EBuiltin BESubNumeric) (TNat 10) ETyApp (EBuiltin BESubNumeric) (TNat 10)
convertPrim _ "BEMulDecimal" (TNumeric10 :-> TNumeric10 :-> TNumeric10) = 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) = 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) = convertPrim _ "BERoundDecimal" (TInt64 :-> TNumeric10 :-> TNumeric10) =
ETyApp (EBuiltin BERoundNumeric) (TNat 10) ETyApp (EBuiltin BERoundNumeric) (TNat 10)
convertPrim _ "BEEqual" (TNumeric10 :-> TNumeric10 :-> TBool) = convertPrim _ "BEEqual" (TNumeric10 :-> TNumeric10 :-> TBool) =

View File

@ -14,6 +14,7 @@ import com.digitalasset.daml.lf.language.{LanguageVersion => LV}
import com.digitalasset.daml_lf.{DamlLf1 => PLF} import com.digitalasset.daml_lf.{DamlLf1 => PLF}
import com.google.protobuf.CodedInputStream import com.google.protobuf.CodedInputStream
import scala.annotation.tailrec
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import scala.collection.{breakOut, mutable} 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 => case PLF.Expr.SumCase.BUILTIN =>
val info = DecodeV1.builtinInfoMap(lfExpr.getBuiltin) val info = DecodeV1.builtinInfoMap(lfExpr.getBuiltin)
assertSince(info.minVersion, lfExpr.getBuiltin.getValueDescriptor.getFullName) assertSince(info.minVersion, lfExpr.getBuiltin.getValueDescriptor.getFullName)
info.maxVersion.foreach(assertUntil(_, lfExpr.getBuiltin.getValueDescriptor.getFullName))
if (info.handleLegacyDecimal) { ntimes[Expr](
// FixMe: https://github.com/digital-asset/daml/issues/2289 info.implicitDecimalScaleParameters,
// enable the check once the compiler produces proper DAML-LF 1.dev ETyApp(_, TDecimalScale),
// info.maxVersion.foreach(assertUntil(_, lfExpr.getBuiltin.getValueDescriptor.getFullName)) EBuiltin(info.builtin))
ETyApp(EBuiltin(info.builtin), TDecimalScale)
} else {
info.maxVersion.foreach(
assertUntil(_, lfExpr.getBuiltin.getValueDescriptor.getFullName))
EBuiltin(info.builtin)
}
case PLF.Expr.SumCase.REC_CON => case PLF.Expr.SumCase.REC_CON =>
val recCon = lfExpr.getRecCon val recCon = lfExpr.getRecCon
@ -791,6 +786,10 @@ private[archive] class DecodeV1(minor: LV.Minor) extends Decode.OfPackage[PLF.Pa
private[lf] object DecodeV1 { 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( case class BuiltinTypeInfo(
proto: PLF.PrimType, proto: PLF.PrimType,
bTyp: BuiltinType, bTyp: BuiltinType,
@ -830,7 +829,7 @@ private[lf] object DecodeV1 {
builtin: BuiltinFunction, builtin: BuiltinFunction,
minVersion: LV = LV.Features.default, // first version that does support the builtin minVersion: LV = LV.Features.default, // first version that does support the builtin
maxVersion: Option[LV] = None, // first version that does not 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] = { val builtinFunctionInfos: List[BuiltinFunctionInfo] = {
@ -840,27 +839,32 @@ private[lf] object DecodeV1 {
ADD_DECIMAL, ADD_DECIMAL,
BAddNumeric, BAddNumeric,
maxVersion = Some(numeric), maxVersion = Some(numeric),
handleLegacyDecimal = true), implicitDecimalScaleParameters = 1
),
BuiltinFunctionInfo( BuiltinFunctionInfo(
SUB_DECIMAL, SUB_DECIMAL,
BSubNumeric, BSubNumeric,
maxVersion = Some(numeric), maxVersion = Some(numeric),
handleLegacyDecimal = true), implicitDecimalScaleParameters = 1
),
BuiltinFunctionInfo( BuiltinFunctionInfo(
MUL_DECIMAL, MUL_DECIMAL,
BMulNumeric, BMulNumeric,
maxVersion = Some(numeric), maxVersion = Some(numeric),
handleLegacyDecimal = true), implicitDecimalScaleParameters = 3
),
BuiltinFunctionInfo( BuiltinFunctionInfo(
DIV_DECIMAL, DIV_DECIMAL,
BDivNumeric, BDivNumeric,
maxVersion = Some(numeric), maxVersion = Some(numeric),
handleLegacyDecimal = true), implicitDecimalScaleParameters = 3
),
BuiltinFunctionInfo( BuiltinFunctionInfo(
ROUND_DECIMAL, ROUND_DECIMAL,
BRoundNumeric, BRoundNumeric,
maxVersion = Some(numeric), maxVersion = Some(numeric),
handleLegacyDecimal = true), implicitDecimalScaleParameters = 1
),
BuiltinFunctionInfo(ADD_NUMERIC, BAddNumeric, minVersion = numeric), BuiltinFunctionInfo(ADD_NUMERIC, BAddNumeric, minVersion = numeric),
BuiltinFunctionInfo(SUB_NUMERIC, BSubNumeric, minVersion = numeric), BuiltinFunctionInfo(SUB_NUMERIC, BSubNumeric, minVersion = numeric),
BuiltinFunctionInfo(MUL_NUMERIC, BMulNumeric, minVersion = numeric), BuiltinFunctionInfo(MUL_NUMERIC, BMulNumeric, minVersion = numeric),
@ -878,12 +882,14 @@ private[lf] object DecodeV1 {
INT64_TO_DECIMAL, INT64_TO_DECIMAL,
BInt64ToNumeric, BInt64ToNumeric,
maxVersion = Some(numeric), maxVersion = Some(numeric),
handleLegacyDecimal = true), implicitDecimalScaleParameters = 1
),
BuiltinFunctionInfo( BuiltinFunctionInfo(
DECIMAL_TO_INT64, DECIMAL_TO_INT64,
BNumericToInt64, BNumericToInt64,
maxVersion = Some(numeric), maxVersion = Some(numeric),
handleLegacyDecimal = true), implicitDecimalScaleParameters = 1
),
BuiltinFunctionInfo(INT64_TO_NUMERIC, BInt64ToNumeric, minVersion = numeric), BuiltinFunctionInfo(INT64_TO_NUMERIC, BInt64ToNumeric, minVersion = numeric),
BuiltinFunctionInfo(NUMERIC_TO_INT64, BNumericToInt64, minVersion = numeric), BuiltinFunctionInfo(NUMERIC_TO_INT64, BNumericToInt64, minVersion = numeric),
BuiltinFunctionInfo(FOLDL, BFoldl), BuiltinFunctionInfo(FOLDL, BFoldl),
@ -901,7 +907,8 @@ private[lf] object DecodeV1 {
LEQ_DECIMAL, LEQ_DECIMAL,
BLessEqNumeric, BLessEqNumeric,
maxVersion = Some(numeric), maxVersion = Some(numeric),
handleLegacyDecimal = true), implicitDecimalScaleParameters = 1
),
BuiltinFunctionInfo(LEQ_NUMERIC, BLessEqNumeric, minVersion = numeric), BuiltinFunctionInfo(LEQ_NUMERIC, BLessEqNumeric, minVersion = numeric),
BuiltinFunctionInfo(LEQ_TEXT, BLessEqText), BuiltinFunctionInfo(LEQ_TEXT, BLessEqText),
BuiltinFunctionInfo(LEQ_TIMESTAMP, BLessEqTimestamp), BuiltinFunctionInfo(LEQ_TIMESTAMP, BLessEqTimestamp),
@ -911,7 +918,8 @@ private[lf] object DecodeV1 {
GEQ_DECIMAL, GEQ_DECIMAL,
BGreaterEqNumeric, BGreaterEqNumeric,
maxVersion = Some(numeric), maxVersion = Some(numeric),
handleLegacyDecimal = true), implicitDecimalScaleParameters = 1
),
BuiltinFunctionInfo(GEQ_NUMERIC, BGreaterEqNumeric, minVersion = numeric), BuiltinFunctionInfo(GEQ_NUMERIC, BGreaterEqNumeric, minVersion = numeric),
BuiltinFunctionInfo(GEQ_TEXT, BGreaterEqText), BuiltinFunctionInfo(GEQ_TEXT, BGreaterEqText),
BuiltinFunctionInfo(GEQ_TIMESTAMP, BGreaterEqTimestamp), BuiltinFunctionInfo(GEQ_TIMESTAMP, BGreaterEqTimestamp),
@ -921,7 +929,8 @@ private[lf] object DecodeV1 {
LESS_DECIMAL, LESS_DECIMAL,
BLessNumeric, BLessNumeric,
maxVersion = Some(numeric), maxVersion = Some(numeric),
handleLegacyDecimal = true), implicitDecimalScaleParameters = 1
),
BuiltinFunctionInfo(LESS_NUMERIC, BLessNumeric, minVersion = numeric), BuiltinFunctionInfo(LESS_NUMERIC, BLessNumeric, minVersion = numeric),
BuiltinFunctionInfo(LESS_TEXT, BLessText), BuiltinFunctionInfo(LESS_TEXT, BLessText),
BuiltinFunctionInfo(LESS_TIMESTAMP, BLessTimestamp), BuiltinFunctionInfo(LESS_TIMESTAMP, BLessTimestamp),
@ -931,7 +940,8 @@ private[lf] object DecodeV1 {
GREATER_DECIMAL, GREATER_DECIMAL,
BGreaterNumeric, BGreaterNumeric,
maxVersion = Some(numeric), maxVersion = Some(numeric),
handleLegacyDecimal = true), implicitDecimalScaleParameters = 1
),
BuiltinFunctionInfo(GREATER_NUMERIC, BGreaterNumeric, minVersion = numeric), BuiltinFunctionInfo(GREATER_NUMERIC, BGreaterNumeric, minVersion = numeric),
BuiltinFunctionInfo(GREATER_TEXT, BGreaterText), BuiltinFunctionInfo(GREATER_TEXT, BGreaterText),
BuiltinFunctionInfo(GREATER_TIMESTAMP, BGreaterTimestamp), BuiltinFunctionInfo(GREATER_TIMESTAMP, BGreaterTimestamp),
@ -941,7 +951,8 @@ private[lf] object DecodeV1 {
TO_TEXT_DECIMAL, TO_TEXT_DECIMAL,
BToTextNumeric, BToTextNumeric,
maxVersion = Some(numeric), maxVersion = Some(numeric),
handleLegacyDecimal = true), implicitDecimalScaleParameters = 1
),
BuiltinFunctionInfo(TO_TEXT_NUMERIC, BToTextNumeric, minVersion = numeric), BuiltinFunctionInfo(TO_TEXT_NUMERIC, BToTextNumeric, minVersion = numeric),
BuiltinFunctionInfo(TO_TEXT_TIMESTAMP, BToTextTimestamp), BuiltinFunctionInfo(TO_TEXT_TIMESTAMP, BToTextTimestamp),
BuiltinFunctionInfo(TO_TEXT_PARTY, BToTextParty, minVersion = partyTextConversions), BuiltinFunctionInfo(TO_TEXT_PARTY, BToTextParty, minVersion = partyTextConversions),
@ -953,7 +964,7 @@ private[lf] object DecodeV1 {
BuiltinFunctionInfo( BuiltinFunctionInfo(
FROM_TEXT_DECIMAL, FROM_TEXT_DECIMAL,
BFromTextNumeric, BFromTextNumeric,
handleLegacyDecimal = true, implicitDecimalScaleParameters = 1,
minVersion = numberParsing, minVersion = numberParsing,
maxVersion = Some(numeric)), maxVersion = Some(numeric)),
BuiltinFunctionInfo(FROM_TEXT_NUMERIC, BFromTextNumeric, minVersion = numeric), BuiltinFunctionInfo(FROM_TEXT_NUMERIC, BFromTextNumeric, minVersion = numeric),
@ -975,7 +986,8 @@ private[lf] object DecodeV1 {
EQUAL_DECIMAL, EQUAL_DECIMAL,
BEqualNumeric, BEqualNumeric,
maxVersion = Some(numeric), maxVersion = Some(numeric),
handleLegacyDecimal = true), implicitDecimalScaleParameters = 1
),
BuiltinFunctionInfo(EQUAL_NUMERIC, BEqualNumeric, minVersion = numeric), BuiltinFunctionInfo(EQUAL_NUMERIC, BEqualNumeric, minVersion = numeric),
BuiltinFunctionInfo(EQUAL_TEXT, BEqualText), BuiltinFunctionInfo(EQUAL_TEXT, BEqualText),
BuiltinFunctionInfo(EQUAL_TIMESTAMP, BEqualTimestamp), BuiltinFunctionInfo(EQUAL_TIMESTAMP, BEqualTimestamp),

View File

@ -7,11 +7,11 @@ import java.math.BigDecimal
import java.nio.file.{Files, Paths} import java.nio.file.{Files, Paths}
import com.digitalasset.daml.bazeltools.BazelRunfiles._ 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.archive.Reader.ParseError
import com.digitalasset.daml.lf.data.{ImmArray, Ref} import com.digitalasset.daml.lf.data.{ImmArray, Ref}
import com.digitalasset.daml.lf.language.Util._ 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 com.digitalasset.daml_lf.DamlLf1
import org.scalatest.prop.TableDrivenPropertyChecks import org.scalatest.prop.TableDrivenPropertyChecks
import org.scalatest.{Inside, Matchers, OptionValues, WordSpec} import org.scalatest.{Inside, Matchers, OptionValues, WordSpec}
@ -217,8 +217,8 @@ class DecodeV1Spec
"decodeExpr" should { "decodeExpr" should {
def toProto(b: BuiltinFunctionInfo) = def toProtoExpr(b: DamlLf1.BuiltinFunction) =
DamlLf1.Expr.newBuilder().setBuiltin(b.proto).build() DamlLf1.Expr.newBuilder().setBuiltin(b).build()
def toDecimalProto(s: String) = def toDecimalProto(s: String) =
DamlLf1.Expr.newBuilder().setPrimLit(DamlLf1.PrimLit.newBuilder().setDecimal(s)).build() DamlLf1.Expr.newBuilder().setPrimLit(DamlLf1.PrimLit.newBuilder().setDecimal(s)).build()
@ -226,39 +226,102 @@ class DecodeV1Spec
def toNumericProto(s: String) = def toNumericProto(s: String) =
DamlLf1.Expr.newBuilder().setPrimLit(DamlLf1.PrimLit.newBuilder().setNumeric(s)).build() DamlLf1.Expr.newBuilder().setPrimLit(DamlLf1.PrimLit.newBuilder().setNumeric(s)).build()
def toScala(b: BuiltinFunctionInfo) = val decimalBuiltinTestCases = Table[DamlLf1.BuiltinFunction, LanguageMinorVersion, Ast.Expr](
Ast.EBuiltin(b.builtin) ("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 numericBuiltinTestCases = Table(
val decimalBuilttins = "numeric builtins" -> "expected output",
DecodeV1.builtinFunctionInfos.filter(_.handleLegacyDecimal) 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 negativeBuiltinTestCases = Table(
val numericBuilttins = { "other builtins" -> "expected output",
val isNumeric = decimalBuilttins.map(_.builtin).toSet // We do not need to test all other builtin
DecodeV1.builtinFunctionInfos.filter(info => DamlLf1.BuiltinFunction.ADD_INT64 -> Ast.EBuiltin(Ast.BAddInt64),
!info.handleLegacyDecimal && isNumeric(info.builtin)) DamlLf1.BuiltinFunction.APPEND_TEXT -> Ast.EBuiltin(Ast.BAppendText)
} )
// 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: _*)
"translate non numeric/decimal builtin as is for any version" in { "translate non numeric/decimal builtin as is for any version" in {
val allVersions = Table("all versions", preNumericMinVersions ++ postNumericMinVersions: _*) val allVersions = Table("all versions", preNumericMinVersions ++ postNumericMinVersions: _*)
forEvery(allVersions) { version => forEvery(allVersions) { version =>
val decoder = moduleDecoder(version) val decoder = moduleDecoder(version)
forEvery(negativeBuiltinTestCases) { info => forEvery(negativeBuiltinTestCases) { (proto, scala) =>
decoder.decodeExpr(toProto(info), "test") shouldBe toScala(info) decoder.decodeExpr(toProtoExpr(proto), "test") shouldBe scala
} }
} }
} }
@ -268,10 +331,9 @@ class DecodeV1Spec
forEvery(preNumericMinVersions) { version => forEvery(preNumericMinVersions) { version =>
val decoder = moduleDecoder(version) val decoder = moduleDecoder(version)
forEvery(decimalBuiltinTestCases) { info => forEvery(decimalBuiltinTestCases) { (proto, minVersion, scala) =>
if (LV.ordering.gteq(LV(LV.Major.V1, version), info.minVersion)) if (LV.Major.V1.minorVersionOrdering.gteq(version, minVersion))
decoder.decodeExpr(toProto(info), "test") shouldBe Ast decoder.decodeExpr(toProtoExpr(proto), "test") shouldBe scala
.ETyApp(toScala(info), Ast.TNat(10))
} }
} }
} }
@ -281,20 +343,19 @@ class DecodeV1Spec
forEvery(preNumericMinVersions) { version => forEvery(preNumericMinVersions) { version =>
val decoder = moduleDecoder(version) val decoder = moduleDecoder(version)
forEvery(numericBuiltinTestCases) { info => forEvery(numericBuiltinTestCases) { (proto, _) =>
an[ParseError] shouldBe thrownBy(decoder.decodeExpr(toProto(info), "test")) an[ParseError] shouldBe thrownBy(decoder.decodeExpr(toProtoExpr(proto), "test"))
} }
} }
} }
"translate Numeric builtins as is if version >= 1.dev" in { "translate Numeric builtins as is if version >= 1.dev" in {
forEvery(preNumericMinVersions) { version => forEvery(postNumericMinVersions) { version =>
val decoder = moduleDecoder(version) val decoder = moduleDecoder(version)
forEvery(numericBuiltinTestCases) { info => forEvery(numericBuiltinTestCases) { (proto, scala) =>
if (LV.ordering.gteq(LV(LV.Major.V1, version), info.minVersion)) decoder.decodeExpr(toProtoExpr(proto), "test") shouldBe scala
decoder.decodeExpr(toProto(info), "test") shouldBe toScala(info)
} }
} }
} }
@ -303,11 +364,11 @@ class DecodeV1Spec
// reactive the test once the decoder is not so lenient // reactive the test once the decoder is not so lenient
"reject Decimal builtins if version >= 1.dev" ignore { "reject Decimal builtins if version >= 1.dev" ignore {
forEvery(preNumericMinVersions) { version => forEvery(postNumericMinVersions) { version =>
val decoder = moduleDecoder(version) val decoder = moduleDecoder(version)
forEvery(decimalBuiltinTestCases) { info => forEvery(decimalBuiltinTestCases) { (proto, _, _) =>
an[ParseError] shouldBe thrownBy(decoder.decodeExpr(toProto(info), "test")) an[ParseError] shouldBe thrownBy(decoder.decodeExpr(toProtoExpr(proto), "test"))
} }
} }
} }

View File

@ -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]] * performed, the [[https://en.wikipedia.org/wiki/Rounding#Round_half_to_even> banker's rounding convention]]
* is applied. * is applied.
* In case of overflow, returns an error message instead. * 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] = { final def multiply(scale: Int, x: Numeric, y: Numeric): Either[String, Numeric] = {
assert(x.scale == y.scale) checkForOverflow((x multiply y).setScale(scale, ROUND_HALF_EVEN))
checkForOverflow((x multiply y).setScale(x.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]] * performed, the [[https://en.wikipedia.org/wiki/Rounding#Round_half_to_even> banker's rounding convention]]
* is applied. * is applied.
* In case of overflow, returns an error message instead. * 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] = { final def divide(scale: Int, x: Numeric, y: Numeric): Either[String, Numeric] = {
assert(x.scale == y.scale) checkForOverflow(x.divide(y, scale, ROUND_HALF_EVEN))
checkForOverflow(x.divide(y, x.scale, ROUND_HALF_EVEN))
} }
/** /**

View File

@ -193,76 +193,85 @@ class NumericSpec
Numeric.assertFromString(s) Numeric.assertFromString(s)
"return an error in case of overflow" in { "return an error in case of overflow" in {
val testCases = Table[Numeric, Numeric]( val testCases = Table[Int, Numeric, Numeric](
("input1", "input2"), ("scale", "input1", "input2"),
("10000000000000000000.", "10000000000000000000."), (0, "10000000000000000000.", "10000000000000000000."),
("10000000000000000000.0", "-1000000000000000000.0"), (1, "10000000000000000000.0", "-1000000000000000000.0"),
("-1000000000000000000.00", "1000000000000000000.00"), (2, "-1000000000000000000.00", "1000000000000000000.00"),
("-100000000000000000.000", "-1000000000000000000.000"), (3, "-100000000000000000.000", "-1000000000000000000.000"),
("10.000000000000000000000000000000000000", "10.000000000000000000000000000000000000"), (36, "10.000000000000000000000000000000000000", "10.000000000000000000000000000000000000"),
("5678901234567890.12345678901234", "-5678901234567890.12345678901234"), (14, "5678901234567890.12345678901234", "-5678901234567890.12345678901234"),
) )
multiply("10000000000000000000.", "1000000000000000000.") shouldBe 'right multiply(0, "10000000000000000000.", "1000000000000000000.") shouldBe 'right
forEvery(testCases) { (x, y) => forEvery(testCases) { (scale, x, y) =>
multiply(x, y) shouldBe 'left multiply(scale, x, y) shouldBe 'left
} }
} }
"multiply two numeric properly" in { "multiply two numeric properly" in {
val testCases = Table[Numeric, Numeric, Numeric]( val testCases = Table[Int, Numeric, Numeric, Numeric](
("input1", "input2", "result"), ("scale", "input1", "input2", "result"),
("0.00000", "0.00000", "0.00000"), (5, "0.00000", "0.00000", "0.00000"),
( (
38,
"0.00000000000000000010000000000000000000", "0.00000000000000000010000000000000000000",
"0.00000000000000000010000000000000000000", "0.00000000000000000010000000000000000000",
"0.00000000000000000000000000000000000001" "0.00000000000000000000000000000000000001"
), ),
( (
37,
"1.0000000000000000000000000000000000000", "1.0000000000000000000000000000000000000",
"-0.0000000000000000000000000000000000001", "-0.0000000000000000000000000000000000001",
"-0.0000000000000000000000000000000000001" "-0.0000000000000000000000000000000000001"
), ),
( (
18,
"-1000000000000000000.000000000000000000", "-1000000000000000000.000000000000000000",
"0.000000000000000001", "0.000000000000000001",
"-1.000000000000000000" "-1.000000000000000000"
), ),
( (
37,
"3.1415926535897932384626433832795028842", "3.1415926535897932384626433832795028842",
"2.7182818284590452353602874713526624978", "2.7182818284590452353602874713526624978",
"8.5397342226735670654635508695465744952" "8.5397342226735670654635508695465744952"
), ),
( (
1,
"0.5", "0.5",
"0.1", "0.1",
"0.0", "0.0",
), ),
( (
2,
"0.15", "0.15",
"0.10", "0.10",
"0.02", "0.02",
), ),
( (
3,
"1.006", "1.006",
"0.100", "0.100",
"0.101" "0.101"
), ),
( (
4,
"2.1003", "2.1003",
"0.1000", "0.1000",
"0.2100" "0.2100"
), ),
( (
0,
"5555555555555555555.", "5555555555555555555.",
"4444444444444444444.", "4444444444444444444.",
"24691358024691358019753086419753086420." "24691358024691358019753086419753086420."
) )
) )
forEvery(testCases) { (x, y, z) => forEvery(testCases) { (scale, x, y, z) =>
multiply(x, y) shouldBe Right(z) multiply(scale, x, y) shouldBe Right(z)
} }
} }
@ -274,77 +283,82 @@ class NumericSpec
implicit def toNumeric(s: String): Numeric = Numeric.assertFromString(s) implicit def toNumeric(s: String): Numeric = Numeric.assertFromString(s)
"return an error in case of overflow" in { "return an error in case of overflow" in {
val testCases = Table[Numeric, Numeric]( val testCases = Table[Int, Numeric, Numeric](
("input1", "input2"), ("scale", "input1", "input2"),
("1000000000000000000.0000000000", "0.0000000001"), (10, "1000000000000000000.0000000000", "0.0000000001"),
("-1000000000000000000000000000000000000.0", "0.1"), (1, "-1000000000000000000000000000000000000.0", "0.1"),
( (
38,
"0.10000000000000000000000000000000000000", "0.10000000000000000000000000000000000000",
"-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) => forEvery(testCases) { (scale, x, y) =>
divide(x, y) shouldBe 'left divide(scale, x, y) shouldBe 'left
} }
} }
"divide two numerics properly" in { "divide two numerics properly" in {
val testCases = Table[Numeric, Numeric, Numeric]( val testCases = Table[Int, Numeric, Numeric, Numeric](
("input1", "input2", "result"), ("scale", "input1", "input2", "result"),
("0.00000", "1.00000", "0.00000"), (5, "0.00000", "1.00000", "0.00000"),
( (
38,
"0.00000000000000000010000000000000000000", "0.00000000000000000010000000000000000000",
"-0.00000000000000000100000000000000000000", "-0.00000000000000000100000000000000000000",
"-0.10000000000000000000000000000000000000" "-0.10000000000000000000000000000000000000"
), ),
( (
37,
"0.0000000000000000000000000000000000001", "0.0000000000000000000000000000000000001",
"-0.1000000000000000000000000000000000001", "-0.1000000000000000000000000000000000001",
"-0.0000000000000000000000000000000000010" "-0.0000000000000000000000000000000000010"
), ),
( (
18,
"1.000000000000000000", "1.000000000000000000",
"-0.000000000000000001", "-0.000000000000000001",
"-1000000000000000000.000000000000000000", "-1000000000000000000.000000000000000000",
), ),
( (
37,
"3.1415926535897932384626433832795028842", "3.1415926535897932384626433832795028842",
"2.7182818284590452353602874713526624978", "2.7182818284590452353602874713526624978",
"1.1557273497909217179100931833126962991" "1.1557273497909217179100931833126962991"
), ),
( (
1,
"1.0", "1.0",
"4.0", "4.0",
"0.2", "0.2",
), ),
(1, "6.0", "8.0", "0.8"),
( (
"6.0", 3,
"8.0",
"0.8",
),
(
"1.006", "1.006",
"10.000", "10.000",
"0.101" "0.101"
), ),
( (
4,
"2.1003", "2.1003",
"10.0000", "10.0000",
"0.2100" "0.2100"
), ),
( (
19,
"5555555555555555555.5555555555555555555", "5555555555555555555.5555555555555555555",
"4343434343434343434.4343434343434343434", "4343434343434343434.4343434343434343434",
"1.2790697674418604651" "1.2790697674418604651"
) )
) )
forEvery(testCases) { (x, y, z) => forEvery(testCases) { (scale, x, y, z) =>
divide(x, y) shouldBe Right(z) divide(scale, x, y) shouldBe Right(z)
} }
} }
} }

View File

@ -157,8 +157,7 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {
case TApp(fun, arg) => fun -> arg case TApp(fun, arg) => fun -> arg
}) })
private def ignoreFirstTNatForDecimalLegacy(typs: ImmArray[Type]): ImmArray[Type] = private def ignoreOneDecimalScaleParameter(typs: ImmArray[Type]): ImmArray[Type] =
// BTNumeric must be applied to a TNat that we should ignore
typs match { typs match {
case ImmArrayCons(TNat(_), tail) => tail case ImmArrayCons(TNat(_), tail) => tail
case _ => case _ =>
@ -196,7 +195,7 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {
case TBuiltin(bType) => case TBuiltin(bType) =>
val (proto, typs) = val (proto, typs) =
if (bType == BTNumeric && versionIsOlderThan(LV.Features.numeric)) if (bType == BTNumeric && versionIsOlderThan(LV.Features.numeric))
PLF.PrimType.DECIMAL -> ignoreFirstTNatForDecimalLegacy(args) PLF.PrimType.DECIMAL -> ignoreOneDecimalScaleParameter(args)
else else
builtinTypeInfoMap(bType).proto -> args builtinTypeInfoMap(bType).proto -> args
builder.setPrim( builder.setPrim(
@ -405,10 +404,10 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {
case ETyAbs(binder, body) => binder -> body case ETyAbs(binder, body) => binder -> body
}) })
private def isLegacyDecimalBuiltin(expr: Expr) = private def implicitDecimalScaleParameters(expr: Expr) =
expr match { expr match {
case EBuiltin(f) => builtinFunctionMap(f).handleLegacyDecimal case EBuiltin(f) => builtinFunctionMap(f).implicitDecimalScaleParameters
case _ => false case _ => 0
} }
private def encodeExprBuilder(expr0: Expr): PLF.Expr.Builder = { private def encodeExprBuilder(expr0: Expr): PLF.Expr.Builder = {
@ -460,11 +459,8 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {
case EApps(fun, args) => case EApps(fun, args) =>
newBuilder.setApp(PLF.Expr.App.newBuilder().setFun(fun).accumulateLeft(args)(_ addArgs _)) newBuilder.setApp(PLF.Expr.App.newBuilder().setFun(fun).accumulateLeft(args)(_ addArgs _))
case ETyApps(expr, typs1) => case ETyApps(expr, typs1) =>
val typs: ImmArray[Type] = val typs =
if (isLegacyDecimalBuiltin(expr) && versionIsOlderThan(LV.Features.numeric)) ntimes(implicitDecimalScaleParameters(expr), ignoreOneDecimalScaleParameter, typs1)
ignoreFirstTNatForDecimalLegacy(typs1)
else
typs1
newBuilder.setTyApp( newBuilder.setTyApp(
PLF.Expr.TyApp.newBuilder().setExpr(expr).accumulateLeft(typs)(_ addTypes _)) PLF.Expr.TyApp.newBuilder().setExpr(expr).accumulateLeft(typs)(_ addTypes _))
case ETyApps(expr, typs) => case ETyApps(expr, typs) =>
@ -602,6 +598,10 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {
object EncodeV1 { 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] { private sealed abstract class LeftRecMatcher[Left, Right] {
def unapply(arg: Left): Option[(Left, ImmArray[Right])] def unapply(arg: Left): Option[(Left, ImmArray[Right])]
} }

View File

@ -134,28 +134,28 @@ object SBuiltin {
Numeric.add(x, y) 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 = private def subtract(x: Numeric, y: Numeric): Numeric =
rightOrArithmeticError( rightOrArithmeticError(
s"(Numeric ${x.scale}) overflow when subtracting ${Numeric.toString(y)} from ${Numeric.toString(x)}.", s"(Numeric ${x.scale}) overflow when subtracting ${Numeric.toString(y)} from ${Numeric.toString(x)}.",
Numeric.subtract(x, y) 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) { sealed abstract class SBBinaryOpNumeric(op: (Numeric, Numeric) => Numeric) extends SBuiltin(3) {
final def execute(args: util.ArrayList[SValue], machine: Machine): Unit = { final def execute(args: util.ArrayList[SValue], machine: Machine): Unit = {
val scale = args.get(0).asInstanceOf[STNat].n 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 SBAddNumeric extends SBBinaryOpNumeric(add)
final case object SBSubNumeric extends SBBinaryOpNumeric(subtract) final case object SBSubNumeric extends SBBinaryOpNumeric(subtract)
final case object SBMulNumeric extends SBBinaryOpNumeric(multiply) final case object SBMulNumeric extends SBBinaryOpNumeric2(multiply)
final case object SBDivNumeric extends SBBinaryOpNumeric(divide) final case object SBDivNumeric extends SBBinaryOpNumeric2(divide)
final case object SBRoundNumeric extends SBuiltin(3) { final case object SBRoundNumeric extends SBuiltin(3) {
def execute(args: util.ArrayList[SValue], machine: Machine): Unit = { def execute(args: util.ArrayList[SValue], machine: Machine): Unit = {

View File

@ -263,15 +263,16 @@ class SBuiltinTest extends FreeSpec with Matchers with TableDrivenPropertyChecks
val overSqrtOfTen = "3.1622776601683793319988935444327185338" val overSqrtOfTen = "3.1622776601683793319988935444327185338"
"throws exception in case of overflow" in { "throws exception in case of overflow" in {
eval(e"$builtin @0 1${"0" * 18}. 1${"0" * 19}.") shouldBe 'right eval(e"$builtin @0 @0 @0 1${"0" * 18}. 1${"0" * 19}.") shouldBe 'right
eval(e"$builtin @0 1${"0" * 19}. 1${"0" * 19}.") shouldBe 'left eval(e"$builtin @0 @0 @0 1${"0" * 19}. 1${"0" * 19}.") shouldBe 'left
eval(e"$builtin @37 $underSqrtOfTen $underSqrtOfTen") shouldBe 'right eval(e"$builtin @37 @37 @37 $underSqrtOfTen $underSqrtOfTen") shouldBe 'right
eval(e"$builtin @37 $overSqrtOfTen $underSqrtOfTen") shouldBe 'left eval(e"$builtin @37 @37 @37 $overSqrtOfTen $underSqrtOfTen") shouldBe 'left
eval(e"$builtin @10 1.1000000000 2.2000000000") shouldBe Right(SNumeric(n(10, 2.42))) eval(e"$builtin @10 @10 @10 1.1000000000 2.2000000000") shouldBe Right(
eval(e"$builtin @10 ${tenPowerOf(13)} ${tenPowerOf(14)}") shouldBe Right( SNumeric(n(10, 2.42)))
eval(e"$builtin @10 @10 @10 ${tenPowerOf(13)} ${tenPowerOf(14)}") shouldBe Right(
SNumeric(n(10, "1E27"))) SNumeric(n(10, "1E27")))
eval(e"$builtin @10 ${tenPowerOf(14)} ${tenPowerOf(14)}") shouldBe 'left eval(e"$builtin @10 @10 @10 ${tenPowerOf(14)} ${tenPowerOf(14)}") shouldBe 'left
eval(e"$builtin @10 ${s(10, bigBigDecimal)} ${bigBigDecimal - 1}") shouldBe Left( eval(e"$builtin @10 @10 @10 ${s(10, bigBigDecimal)} ${bigBigDecimal - 1}") shouldBe Left(
DamlEArithmeticError( DamlEArithmeticError(
s"(Numeric 10) overflow when multiplying ${s(10, bigBigDecimal)} by ${s(10, bigBigDecimal - 1)}.") 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" - { "DIV_NUMERIC" - {
val builtin = "DIV_NUMERIC" val builtin = "DIV_NUMERIC"
"throws exception in case of overflow" in { "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 @38 @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 @38 @38 @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 @1 @1 ${s(1, "1E36")} 0.2") shouldBe 'right
eval(e"$builtin @1 ${s(1, "1E36")} 0.1") shouldBe 'left eval(e"$builtin @1 @1 @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 @10 @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 @10 @10 ${s(10, bigBigDecimal)} ${tenPowerOf(-10)}") shouldBe 'left
eval(e"$builtin @10 ${tenPowerOf(17)} ${tenPowerOf(-10)}") shouldBe Right( eval(e"$builtin @10 @10 @10 ${tenPowerOf(17)} ${tenPowerOf(-10)}") shouldBe Right(
SNumeric(n(10, "1E27"))) 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( DamlEArithmeticError(
s"(Numeric 10) overflow when dividing ${tenPowerOf(18)} by ${tenPowerOf(-10)}.") s"(Numeric 10) overflow when dividing ${tenPowerOf(18)} by ${tenPowerOf(-10)}.")
) )
} }
"throws exception when divided by 0" in { "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)))) SNumeric(n(10, tenPowerOf(10))))
eval(e"$builtin @10 ${s(10, one)} ${s(10, zero)}") shouldBe 'left eval(e"$builtin @10 @10 @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, bigBigDecimal)} ${s(10, zero)}") shouldBe Left(
DamlEArithmeticError(s"Attempt to divide ${s(10, bigBigDecimal)} by 0.0000000000.") 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]]( val testCases = Table[String, (Numeric, Numeric) => Either[Any, SValue]](
("builtin", "reference"), ("builtin", "reference"),
("ADD_NUMERIC", (a, b) => Right(SNumeric(n(10, a add b)))), ("ADD_NUMERIC @10", (a, b) => Right(SNumeric(n(10, a add b)))),
("SUB_NUMERIC", (a, b) => Right(SNumeric(n(10, a subtract b)))), ("SUB_NUMERIC @10", (a, b) => Right(SNumeric(n(10, a subtract b)))),
("MUL_NUMERIC", (a, b) => Right(SNumeric(round(a multiply 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))), ())), (a, b) => Either.cond(b.signum != 0, SNumeric(round(BigDecimal(a) / BigDecimal(b))), ())),
("LESS_EQ_NUMERIC", (a, b) => Right(SBool(BigDecimal(a) <= BigDecimal(b)))), ("LESS_EQ_NUMERIC @10", (a, b) => Right(SBool(BigDecimal(a) <= BigDecimal(b)))),
("GREATER_EQ_NUMERIC", (a, b) => Right(SBool(BigDecimal(a) >= BigDecimal(b)))), ("GREATER_EQ_NUMERIC @10", (a, b) => Right(SBool(BigDecimal(a) >= BigDecimal(b)))),
("LESS_NUMERIC", (a, b) => Right(SBool(BigDecimal(a) < BigDecimal(b)))), ("LESS_NUMERIC @10", (a, b) => Right(SBool(BigDecimal(a) < BigDecimal(b)))),
("GREATER_NUMERIC", (a, b) => Right(SBool(BigDecimal(a) > BigDecimal(b)))), ("GREATER_NUMERIC @10", (a, b) => Right(SBool(BigDecimal(a) > BigDecimal(b)))),
("EQUAL_NUMERIC", (a, b) => Right(SBool(BigDecimal(a) == BigDecimal(b)))), ("EQUAL_NUMERIC @10", (a, b) => Right(SBool(BigDecimal(a) == BigDecimal(b)))),
) )
forEvery(testCases) { (builtin, ref) => forEvery(testCases) { (builtin, ref) =>
forEvery(decimals) { a => forEvery(decimals) { a =>
forEvery(decimals) { b => 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)) .map(_ => ()) shouldBe ref(n(10, a), n(10, b))
} }
} }

View File

@ -314,8 +314,8 @@ object Ast {
// Numeric arithmetic // Numeric arithmetic
final case object BAddNumeric extends BuiltinFunction(2) // : s. Numeric s Numeric s Numeric s 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 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 BMulNumeric extends BuiltinFunction(2) // : s1 s2 s. Numeric s1 Numeric s2 Numeric s
final case object BDivNumeric extends BuiltinFunction(2) // : s. Numeric s Numeric s 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 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 BCastNumeric extends BuiltinFunction(1) // : s1 s2. Numeric s1 Numeric s2
final case object BShiftNumeric extends BuiltinFunction(1) // : s1 s2. Numeric s1 Numeric s2 final case object BShiftNumeric extends BuiltinFunction(1) // : s1 s2. Numeric s1 Numeric s2

View File

@ -2156,33 +2156,23 @@ Numeric functions
scale of the inputs and the output is given by the type parameter scale of the inputs and the output is given by the type parameter
`α`. Throws an error if overflow. `α`. 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 multiple of ``10⁻ᵅ`` using `banker's rounding convention
<https://en.wikipedia.org/wiki/Rounding#Round_half_to_even>`_. The <https://en.wikipedia.org/wiki/Rounding#Round_half_to_even>`_.
scale of the inputs and the output is given by the type parameter The type parameters `α₁`, `α₂`, `α` define the scale of the first
`α`. Throws an error in case of overflow. 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 Divides the first decimal by the second one and rounds the result to
the closest multiple of ``10⁻ᵅ`` using `banker's rounding convention the closest multiple of ``10⁻ᵅ`` using `banker's rounding convention
<https://en.wikipedia.org/wiki/Rounding#Round_half_to_even>`_ (where <https://en.wikipedia.org/wiki/Rounding#Round_half_to_even>`_ (where
`n` is given as the type parameter). The scale of the inputs and `n` is given as the type parameter). The type parameters `α₁`,
the output is given by the type parameter `α`. Throws an error in `α₂`, `α` define the scale of the first input, the second input, and
case of overflow. the output, respectively. 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.
* ``CAST_NUMERIC : ∀ (α₁, α₂: nat) . 'Numeric' α₁ → 'Numeric' α₂`` * ``CAST_NUMERIC : ∀ (α₁, α₂: nat) . 'Numeric' α₁ → 'Numeric' α₂``

View File

@ -42,8 +42,15 @@ private[validation] object Typing {
protected[validation] lazy val typeOfBuiltinFunction = { protected[validation] lazy val typeOfBuiltinFunction = {
val alpha = TVar(Name.assertFromString("$alpha$")) val alpha = TVar(Name.assertFromString("$alpha$"))
val beta = TVar(Name.assertFromString("$beta$")) val beta = TVar(Name.assertFromString("$beta$"))
val gamma = TVar(Name.assertFromString("$beta$"))
def tBinop(typ: Type): Type = typ ->: typ ->: typ def tBinop(typ: Type): Type = typ ->: typ ->: typ
val tNumBinop = TForall(alpha.name -> KNat, tBinop(TNumeric(alpha))) 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 = val tNumConversion =
TForall(alpha.name -> KNat, TForall(beta.name -> KNat, TNumeric(alpha) ->: TNumeric(beta))) TForall(alpha.name -> KNat, TForall(beta.name -> KNat, TNumeric(alpha) ->: TNumeric(beta)))
def tComparison(bType: BuiltinType): Type = TBuiltin(bType) ->: TBuiltin(bType) ->: TBool def tComparison(bType: BuiltinType): Type = TBuiltin(bType) ->: TBuiltin(bType) ->: TBool
@ -54,8 +61,8 @@ private[validation] object Typing {
// Numeric arithmetic // Numeric arithmetic
BAddNumeric -> tNumBinop, BAddNumeric -> tNumBinop,
BSubNumeric -> tNumBinop, BSubNumeric -> tNumBinop,
BMulNumeric -> tNumBinop, BMulNumeric -> tMultiNumBinop,
BDivNumeric -> tNumBinop, BDivNumeric -> tMultiNumBinop,
BRoundNumeric -> TForall(alpha.name -> KNat, TInt64 ->: TNumeric(alpha) ->: TNumeric(alpha)), BRoundNumeric -> TForall(alpha.name -> KNat, TInt64 ->: TNumeric(alpha) ->: TNumeric(alpha)),
BCastNumeric -> tNumConversion, BCastNumeric -> tNumConversion,
BShiftNumeric -> tNumConversion, BShiftNumeric -> tNumConversion,

View File

@ -12,3 +12,4 @@ HEAD — ongoing
* [DAML Docs] suppress instance documentation when `--data-only` mode is requested * [DAML Docs] suppress instance documentation when `--data-only` mode is requested
+ [DAML-LF] add CAST_NUMERIC and SHIFT_NUMERIC in DAML-LF 1.dev + [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. + [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