From 819d9cfce7aeec9e1ae8eb7f54748e13e9ed40f2 Mon Sep 17 00:00:00 2001 From: juerg Date: Tue, 12 Mar 2024 22:17:21 +0100 Subject: [PATCH] added math function `clamp` --- ChangeLog.md | 2 +- .../cheatsheet/section/MathSection.java | 1 + .../venice/impl/functions/MathFunctions.java | 57 ++++++++++++++++++- .../impl/functions/MathFunctionsTest.java | 45 +++++++++++++++ 4 files changed, 102 insertions(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 2cc3b5ea7..d86a49e06 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -10,7 +10,7 @@ All notable changes to this project will be documented in this file. ### Added -- ... +- math function `clamp` diff --git a/src/main/java/com/github/jlangch/venice/impl/docgen/cheatsheet/section/MathSection.java b/src/main/java/com/github/jlangch/venice/impl/docgen/cheatsheet/section/MathSection.java index 85a13fb19..d96a0e013 100644 --- a/src/main/java/com/github/jlangch/venice/impl/docgen/cheatsheet/section/MathSection.java +++ b/src/main/java/com/github/jlangch/venice/impl/docgen/cheatsheet/section/MathSection.java @@ -50,6 +50,7 @@ public DocSection section() { arithmetic.addItem(diBuilder.getDocItem("dec")); arithmetic.addItem(diBuilder.getDocItem("min")); arithmetic.addItem(diBuilder.getDocItem("max")); + arithmetic.addItem(diBuilder.getDocItem("clamp")); arithmetic.addItem(diBuilder.getDocItem("mod")); arithmetic.addItem(diBuilder.getDocItem("mod-floor")); arithmetic.addItem(diBuilder.getDocItem("abs")); diff --git a/src/main/java/com/github/jlangch/venice/impl/functions/MathFunctions.java b/src/main/java/com/github/jlangch/venice/impl/functions/MathFunctions.java index 5f71138d2..1a5ef4d36 100644 --- a/src/main/java/com/github/jlangch/venice/impl/functions/MathFunctions.java +++ b/src/main/java/com/github/jlangch/venice/impl/functions/MathFunctions.java @@ -526,7 +526,7 @@ public VncVal apply(final VncList args) { "(max 1.0)", "(max 1.0 2.0)", "(max 4.0 3.0 2.0 1.0)", "(max 1.0M)", "(max 1.0M 2.0M)", "(max 4.0M 3.0M 2.0M 1.0M)", "(max 1.0M 2)") - .seeAlso("min") + .seeAlso("min", "clamp") .build() ) { @Override @@ -568,7 +568,7 @@ else if (op != Nil){ "(min 1.0)", "(min 1.0 2.0)", "(min 4.0 3.0 2.0 1.0)", "(min 1.0M)", "(min 1.0M 2.0M)", "(min 4.0M 3.0M 2.0M 1.0M)", "(min 1.0M 2)") - .seeAlso("max") + .seeAlso("max", "clamp") .build() ) { @Override @@ -597,6 +597,58 @@ else if (op != Nil) { private static final long serialVersionUID = -1848883965231344442L; }; + public static VncFunction clamp = + new VncFunction( + "clamp", + VncFunction + .meta() + .arglists("(clamp x min max)") + .doc( + "Restricts a given value between a lower and upper bound. " + + "In this way, it acts like a combination of the `min` and `max` functions.") + .examples( + "(clamp 1 10 20)", + "(clamp 1I 10I 20I)", + "(clamp 1.0 10.0 20.0)") + .seeAlso("min", "max") + .build() + ) { + @Override + public VncVal apply(final VncList args) { + ArityExceptions.assertArity(this, args, 3); + + final VncVal v = args.first(); + final VncVal min = args.second(); + final VncVal max = args.third(); + + if (!Types.isVncNumber(v)) { + throw new VncException(String.format( + "Function 'clamp' does not allow %s as value", + Types.getType(v))); + } + if (!Types.isVncNumber(min)) { + throw new VncException(String.format( + "Function 'clamp' does not allow %s as min value", + Types.getType(min))); + } + if (!Types.isVncNumber(max)) { + throw new VncException(String.format( + "Function 'clamp' does not allow %s as max value", + Types.getType(max))); + } + + if (v.compareTo(min) < 0) { + return min; + } + else if (v.compareTo(max) > 0) { + return max; + } + return v; + } + + private static final long serialVersionUID = -1848883965231344442L; + }; + public static VncFunction abs = new VncFunction( "abs", @@ -2166,6 +2218,7 @@ private static VncVal median(final VncList sortedData) { .add(sgn) .add(min) .add(max) + .add(clamp) .add(negate) .add(floor) .add(ceil) diff --git a/src/test/java/com/github/jlangch/venice/impl/functions/MathFunctionsTest.java b/src/test/java/com/github/jlangch/venice/impl/functions/MathFunctionsTest.java index ee5a5ef40..1c61e70e3 100644 --- a/src/test/java/com/github/jlangch/venice/impl/functions/MathFunctionsTest.java +++ b/src/test/java/com/github/jlangch/venice/impl/functions/MathFunctionsTest.java @@ -611,6 +611,51 @@ public void test_min() { assertEquals(new BigDecimal("3.0"), venice.eval("(min 4N 3.0M)")); } + @Test + public void test_clamp() { + final Venice venice = new Venice(); + + // Integer + assertEquals(Integer.valueOf(5), venice.eval("(clamp 4I 5I 8I)")); + assertEquals(Integer.valueOf(5), venice.eval("(clamp 5I 5I 8I)")); + assertEquals(Integer.valueOf(6), venice.eval("(clamp 6I 5I 8I)")); + assertEquals(Integer.valueOf(7), venice.eval("(clamp 7I 5I 8I)")); + assertEquals(Integer.valueOf(8), venice.eval("(clamp 8I 5I 8I)")); + assertEquals(Integer.valueOf(8), venice.eval("(clamp 9I 5I 8I)")); + + // Long + assertEquals(Long.valueOf(5), venice.eval("(clamp 4 5 8)")); + assertEquals(Long.valueOf(5), venice.eval("(clamp 5 5 8)")); + assertEquals(Long.valueOf(6), venice.eval("(clamp 6 5 8)")); + assertEquals(Long.valueOf(7), venice.eval("(clamp 7 5 8)")); + assertEquals(Long.valueOf(8), venice.eval("(clamp 8 5 8)")); + assertEquals(Long.valueOf(8), venice.eval("(clamp 9 5 8)")); + + // Double + assertEquals(Double.valueOf(5.0D), venice.eval("(clamp 4.0 5.0 8.0)")); + assertEquals(Double.valueOf(5.0D), venice.eval("(clamp 5.0 5.0 8.0)")); + assertEquals(Double.valueOf(6.0D), venice.eval("(clamp 6.0 5.0 8.0)")); + assertEquals(Double.valueOf(7.0D), venice.eval("(clamp 7.0 5.0 8.0)")); + assertEquals(Double.valueOf(8.0D), venice.eval("(clamp 8.0 5.0 8.0)")); + assertEquals(Double.valueOf(8.0D), venice.eval("(clamp 9.0 5.0 8.0)")); + + // Decimal + assertEquals(new BigDecimal("5.0"), venice.eval("(clamp 4.0M 5.0M 8.0M)")); + assertEquals(new BigDecimal("5.0"), venice.eval("(clamp 5.0M 5.0M 8.0M)")); + assertEquals(new BigDecimal("6.0"), venice.eval("(clamp 6.0M 5.0M 8.0M)")); + assertEquals(new BigDecimal("7.0"), venice.eval("(clamp 7.0M 5.0M 8.0M)")); + assertEquals(new BigDecimal("8.0"), venice.eval("(clamp 8.0M 5.0M 8.0M)")); + assertEquals(new BigDecimal("8.0"), venice.eval("(clamp 9.0M 5.0M 8.0M)")); + + // BigInteger + assertEquals(new BigInteger("5"), venice.eval("(clamp 4N 5N 8N)")); + assertEquals(new BigInteger("5"), venice.eval("(clamp 5N 5N 8N)")); + assertEquals(new BigInteger("6"), venice.eval("(clamp 6N 5N 8N)")); + assertEquals(new BigInteger("7"), venice.eval("(clamp 7N 5N 8N)")); + assertEquals(new BigInteger("8"), venice.eval("(clamp 8N 5N 8N)")); + assertEquals(new BigInteger("8"), venice.eval("(clamp 9N 5N 8N)")); + } + @Test public void test_mean() { final Venice venice = new Venice();