Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions be/src/exprs/function/math.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,18 @@ struct SqrtName {
using FunctionSqrt =
FunctionMathUnaryAlwayNullable<UnaryFunctionPlainAlwayNullable<SqrtName, std::sqrt>>;

struct GammaName {
static constexpr auto name = "gamma";
// gamma has a pole at zero (negative zero included) and at every negative integer. Those
// are domain errors, and like the other math functions here they are reported as NULL
// instead of the infinity or NaN the C library returns; only overflow becomes Infinity.
static constexpr bool is_invalid_input(Float64 x) {
return x == 0.0 || (x < 0.0 && x == std::floor(x));
}
};
using FunctionGamma =
FunctionMathUnaryAlwayNullable<UnaryFunctionPlainAlwayNullable<GammaName, std::tgamma>>;

struct CbrtName {
static constexpr auto name = "cbrt";
};
Expand Down Expand Up @@ -962,6 +974,7 @@ void register_function_math(SimpleFunctionFactory& factory) {
factory.register_function<FunctionSqrt>();
factory.register_alias("sqrt", "dsqrt");
factory.register_function<FunctionCbrt>();
factory.register_function<FunctionGamma>();
factory.register_function<FunctionTan>();
factory.register_function<FunctionTanh>();
factory.register_function<FunctionCot>();
Expand Down
35 changes: 35 additions & 0 deletions be/test/exprs/function/function_math_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,41 @@ TEST(MathFunctionTest, cbrt_test) {
static_cast<void>(check_function<DataTypeFloat64, true>(func_name, input_types, data_set));
}

TEST(MathFunctionTest, gamma_test) {
std::string func_name = "gamma"; // gamma(x): x > 0, and a negative non-integer x

InputTypeSet input_types = {PrimitiveType::TYPE_DOUBLE};
// Gamma(n) is (n - 1)! for a positive integer n, but std::tgamma does not return every
// factorial exactly: 5 comes back as 24.000000000000004, so the expectation carries the ulp
// the implementation actually produces rather than the mathematical integer. Gamma(0.5) is
// sqrt(pi) and the half-integer rows are its multiples (sqrt(pi)/2 at 1.5, 3*sqrt(pi)/4 at
// 2.5); the values at -0.5, -1.5 and -2.5 come from the reflection formula. 0 (negative
// zero included) and the negative integers are poles, so they are domain errors and come
// back as NULL, and a NULL input stays NULL.
DataSet data_set = {{{1.0}, 1.0},
{{2.0}, 1.0},
{{3.0}, 2.0},
{{4.0}, 6.0},
{{5.0}, 24.000000000000004},
{{10.0}, 362880.00000000047},
{{0.5}, 1.7724538509055161},
{{1.5}, 0.88622692545275805},
{{2.5}, 1.329340388179137},
{{-0.5}, -3.5449077018110318},
{{-1.5}, 2.3632718012073544},
{{-2.5}, -0.94530872048294179},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These exact expected values are from the FE exp(logGamma) route, not the registered BE call. On this runner std::tgamma(5) is 24.0, tgamma(10) is 362880.0, tgamma(-1.5) is 2.363271801207355, tgamma(-2.5) is -0.9453087204829419, and tgamma(171) is 7.257415615307999e306, while this test expects different doubles. check_function compares ColumnFloat64 values exactly, so the unit test (and the generated regression fixture) is platform-dependent and can fail. Please regenerate expectations from the BE implementation or use a documented tolerance/portable oracle.

{{0.0}, Null()},
{{-0.0}, Null()},
{{-1.0}, Null()},
{{-2.0}, Null()},
{{-3.0}, Null()},
{{-10.0}, Null()},
{{Null()}, Null()}};

static_cast<void>(
check_function_all_arg_comb<DataTypeFloat64, true>(func_name, input_types, data_set));
}

TEST(MathFunctionTest, cot_test) {
std::string func_name = "cot";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@
import org.apache.doris.nereids.trees.expressions.functions.scalar.FromSecond;
import org.apache.doris.nereids.trees.expressions.functions.scalar.FromUnixtime;
import org.apache.doris.nereids.trees.expressions.functions.scalar.G;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Gamma;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Gcd;
import org.apache.doris.nereids.trees.expressions.functions.scalar.GetFormat;
import org.apache.doris.nereids.trees.expressions.functions.scalar.GetVariantType;
Expand Down Expand Up @@ -823,6 +824,7 @@ public class BuiltinScalarFunctions implements FunctionHelper {
scalar(FromIso8601Date.class, "from_iso8601_date"),
scalar(FromUnixtime.class, "from_unixtime"),
scalar(G.class, "g"),
scalar(Gamma.class, "gamma"),
scalar(Gcd.class, "gcd"),
scalar(GetFormat.class, "get_format"),
scalar(GetVariantType.class, "variant_type"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.apache.doris.nereids.trees.expressions.functions.scalar;

import org.apache.doris.catalog.FunctionSignature;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.functions.AlwaysNullable;
import org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
import org.apache.doris.nereids.trees.expressions.functions.PropagateNullLiteral;
import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression;
import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
import org.apache.doris.nereids.types.DoubleType;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;

import java.util.List;

/**
* ScalarFunction 'gamma'. This class is generated by GenerateFunction.
*
* <p>Gamma(x) generalises the factorial to real numbers, so gamma(n) is (n - 1)! for a
* positive integer n. It has a pole at zero and at every negative integer, where this
* function returns NULL instead of the NaN that the mathematical definition would produce.
*/
public class Gamma extends ScalarFunction
implements UnaryExpression, ExplicitlyCastableSignature, AlwaysNullable, PropagateNullLiteral {

public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
FunctionSignature.ret(DoubleType.INSTANCE).args(DoubleType.INSTANCE)
);

/**
* constructor with 1 argument.
*/
public Gamma(Expression arg) {
super("gamma", arg);
}

/** constructor for withChildren and reuse signature */
private Gamma(ScalarFunctionParams functionParams) {
super(functionParams);
}

/**
* withChildren.
*/
@Override
public Gamma withChildren(List<Expression> children) {
Preconditions.checkArgument(children.size() == 1);
return new Gamma(getFunctionParams(children));
}

@Override
public List<FunctionSignature> getSignatures() {
return SIGNATURES;
}

@Override
public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
return visitor.visitGamma(this, context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@
import org.apache.doris.nereids.trees.expressions.functions.scalar.FromIso8601Date;
import org.apache.doris.nereids.trees.expressions.functions.scalar.FromUnixtime;
import org.apache.doris.nereids.trees.expressions.functions.scalar.G;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Gamma;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Gcd;
import org.apache.doris.nereids.trees.expressions.functions.scalar.GetFormat;
import org.apache.doris.nereids.trees.expressions.functions.scalar.GetVariantType;
Expand Down Expand Up @@ -1562,6 +1563,10 @@ default R visitG(G g, C context) {
return visitScalarFunction(g, context);
}

default R visitGamma(Gamma gamma, C context) {
return visitScalarFunction(gamma, context);
}

default R visitGcd(Gcd gcd, C context) {
return visitScalarFunction(gcd, context);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.apache.doris.nereids.trees.expressions.functions.scalar;

import org.apache.doris.catalog.FunctionSignature;
import org.apache.doris.nereids.rules.expression.ExpressionRewriteTestHelper;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.literal.DoubleLiteral;
import org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionRewriter;
import org.apache.doris.nereids.types.DoubleType;

import com.google.common.collect.ImmutableList;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class GammaTest extends ExpressionRewriteTestHelper {

@Test
public void testSignatureAndNullability() {
Gamma gamma = new Gamma(new DoubleLiteral(2.5));

Assertions.assertEquals(DoubleType.INSTANCE, gamma.getDataType());
Assertions.assertEquals(1, gamma.getSignatures().size());
FunctionSignature signature = gamma.getSignatures().get(0);
Assertions.assertEquals(DoubleType.INSTANCE, signature.returnType);
Assertions.assertEquals(ImmutableList.of(DoubleType.INSTANCE), signature.argumentsTypes);
// gamma has poles at zero and at every negative integer, so the result stays nullable
// however non-nullable the argument is
Assertions.assertTrue(gamma.nullable());
Assertions.assertEquals("gamma(2.5)", gamma.toSql());
}

@Test
public void testAnalyzedArgumentIsCastToDouble() {
Expression analyzed = typeCoercion(PARSER.parseExpression("gamma(5)"));

Assertions.assertTrue(analyzed instanceof Gamma);
Assertions.assertEquals(DoubleType.INSTANCE, analyzed.getDataType());
Assertions.assertEquals(DoubleType.INSTANCE, analyzed.child(0).getDataType());
Assertions.assertTrue(analyzed.nullable());
}

@Test
public void testAcceptRebuildsThroughWithChildren() {
Gamma gamma = new Gamma(new DoubleLiteral(2.5));

Expression rewritten = gamma.accept(new DefaultExpressionRewriter<Void>() {
@Override
public Expression visitDoubleLiteral(DoubleLiteral doubleLiteral, Void context) {
return new DoubleLiteral(doubleLiteral.getValue() + 1.0);
}
}, null);

Assertions.assertEquals(new Gamma(new DoubleLiteral(3.5)), rewritten);
Assertions.assertEquals(DoubleType.INSTANCE, rewritten.getDataType());
}

@Test
public void testWithChildrenRejectsWrongArity() {
Gamma gamma = new Gamma(new DoubleLiteral(2.5));

// The arity guard is the only branch in this class, so the failing direction has to be
// exercised as well: a partially covered line counts as uncovered for the increment
// coverage gate.
Assertions.assertThrows(IllegalArgumentException.class,
() -> gamma.withChildren(ImmutableList.of(new DoubleLiteral(1.0), new DoubleLiteral(2.0))));
}
}
Loading
Loading