diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/scalb.md b/docs/reference/query-languages/esql/_snippets/functions/description/scalb.md
new file mode 100644
index 0000000000000..c71133d8ae925
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/description/scalb.md
@@ -0,0 +1,11 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
+
+**Description**
+
+Returns the result of `d * 2 ^ scaleFactor`.
+
+::::{note}
+Similar to Java's `scalb` function. Result is rounded as if performed by a single correctly rounded floating-point multiply to a member of the double value set.
+::::
+
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/scalb.md b/docs/reference/query-languages/esql/_snippets/functions/examples/scalb.md
new file mode 100644
index 0000000000000..b5927cea9fbb8
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/examples/scalb.md
@@ -0,0 +1,16 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
+
+**Examples**
+
+```esql
+null
+```
+
+The scaleFactor can be a fraction, which is similar to performing a root.
+For example, the scaleFactor of `0.5` will give the square root of the d:
+
+```esql
+null
+```
+
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/layout/scalb.md b/docs/reference/query-languages/esql/_snippets/functions/layout/scalb.md
new file mode 100644
index 0000000000000..15022863d204b
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/layout/scalb.md
@@ -0,0 +1,23 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
+
+## `SCALB` [esql-scalb]
+
+**Syntax**
+
+:::{image} ../../../images/functions/scalb.svg
+:alt: Embedded
+:class: text-center
+:::
+
+
+:::{include} ../parameters/scalb.md
+:::
+
+:::{include} ../description/scalb.md
+:::
+
+:::{include} ../types/scalb.md
+:::
+
+:::{include} ../examples/scalb.md
+:::
diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/scalb.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/scalb.md
new file mode 100644
index 0000000000000..4e4e726c372dc
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/scalb.md
@@ -0,0 +1,10 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
+
+**Parameters**
+
+`d`
+: Numeric expression for the multiplier. If `null`, the function returns `null`.
+
+`scaleFactor`
+: Numeric expression for the scale factor. If `null`, the function returns `null`.
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/scalb.md b/docs/reference/query-languages/esql/_snippets/functions/types/scalb.md
new file mode 100644
index 0000000000000..4fab8db35a531
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/types/scalb.md
@@ -0,0 +1,15 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
+
+**Supported types**
+
+| d | scaleFactor | result |
+| --- | --- | --- |
+| double | integer | double |
+| double | long | double |
+| integer | integer | double |
+| integer | long | double |
+| long | integer | double |
+| long | long | double |
+| unsigned_long | integer | double |
+| unsigned_long | long | double |
+
diff --git a/docs/reference/query-languages/esql/images/functions/scalb.svg b/docs/reference/query-languages/esql/images/functions/scalb.svg
new file mode 100644
index 0000000000000..161e05c163c91
--- /dev/null
+++ b/docs/reference/query-languages/esql/images/functions/scalb.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/scalb.json b/docs/reference/query-languages/esql/kibana/definition/functions/scalb.json
new file mode 100644
index 0000000000000..4a122159af939
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/definition/functions/scalb.json
@@ -0,0 +1,159 @@
+{
+ "comment" : "This is generated by ESQL’s AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.",
+ "type" : "scalar",
+ "name" : "scalb",
+ "description" : "Returns the result of `d * 2 ^ scaleFactor`.",
+ "note" : "Similar to Java's `scalb` function. Result is rounded as if performed by a single correctly rounded floating-point multiply to a member of the double value set.",
+ "signatures" : [
+ {
+ "params" : [
+ {
+ "name" : "d",
+ "type" : "double",
+ "optional" : false,
+ "description" : "Numeric expression for the multiplier. If `null`, the function returns `null`."
+ },
+ {
+ "name" : "scaleFactor",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "Numeric expression for the scale factor. If `null`, the function returns `null`."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "d",
+ "type" : "double",
+ "optional" : false,
+ "description" : "Numeric expression for the multiplier. If `null`, the function returns `null`."
+ },
+ {
+ "name" : "scaleFactor",
+ "type" : "long",
+ "optional" : false,
+ "description" : "Numeric expression for the scale factor. If `null`, the function returns `null`."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "d",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "Numeric expression for the multiplier. If `null`, the function returns `null`."
+ },
+ {
+ "name" : "scaleFactor",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "Numeric expression for the scale factor. If `null`, the function returns `null`."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "d",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "Numeric expression for the multiplier. If `null`, the function returns `null`."
+ },
+ {
+ "name" : "scaleFactor",
+ "type" : "long",
+ "optional" : false,
+ "description" : "Numeric expression for the scale factor. If `null`, the function returns `null`."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "d",
+ "type" : "long",
+ "optional" : false,
+ "description" : "Numeric expression for the multiplier. If `null`, the function returns `null`."
+ },
+ {
+ "name" : "scaleFactor",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "Numeric expression for the scale factor. If `null`, the function returns `null`."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "d",
+ "type" : "long",
+ "optional" : false,
+ "description" : "Numeric expression for the multiplier. If `null`, the function returns `null`."
+ },
+ {
+ "name" : "scaleFactor",
+ "type" : "long",
+ "optional" : false,
+ "description" : "Numeric expression for the scale factor. If `null`, the function returns `null`."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "d",
+ "type" : "unsigned_long",
+ "optional" : false,
+ "description" : "Numeric expression for the multiplier. If `null`, the function returns `null`."
+ },
+ {
+ "name" : "scaleFactor",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "Numeric expression for the scale factor. If `null`, the function returns `null`."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "d",
+ "type" : "unsigned_long",
+ "optional" : false,
+ "description" : "Numeric expression for the multiplier. If `null`, the function returns `null`."
+ },
+ {
+ "name" : "scaleFactor",
+ "type" : "long",
+ "optional" : false,
+ "description" : "Numeric expression for the scale factor. If `null`, the function returns `null`."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ }
+ ],
+ "examples" : [
+ null,
+ null
+ ],
+ "preview" : false,
+ "snapshot_only" : false
+}
diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/scalb.md b/docs/reference/query-languages/esql/kibana/docs/functions/scalb.md
new file mode 100644
index 0000000000000..d857f3ec9acf8
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/docs/functions/scalb.md
@@ -0,0 +1,9 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
+
+### SCALB
+Returns the result of `d * 2 ^ scaleFactor`.
+
+```esql
+null
+```
+Note: Similar to Java's `scalb` function. Result is rounded as if performed by a single correctly rounded floating-point multiply to a member of the double value set.
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/floats.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/floats.csv-spec
index 0e5b28b6966af..675118432504d 100644
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/floats.csv-spec
+++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/floats.csv-spec
@@ -530,7 +530,7 @@ required_capability: agg_values
mvAppend
required_capability: fn_mv_append
-FROM employees
+ FROM employees
| WHERE emp_no == 10008 OR emp_no == 10021
| EVAL d = mv_append(salary_change, salary_change),
i = mv_append(salary_change.int, salary_change.int),
@@ -592,3 +592,42 @@ s:double | emp_no:integer | salary:integer | salary_change:double
-1.0 | 10004 | 36174 | [-0.35, 1.13, 3.65, 13.48]
-1.0 | 10005 | 63528 | [-2.14, 13.07]
;
+
+scalb
+required_capability: fn_scalb
+
+row x = 3.0, y = 10 | eval z = scalb(x, y);
+
+x:double | y:integer | z:double
+3.0 | 10 | 3072.0
+;
+
+scalbWithNonFoldableArgs
+required_capability: fn_scalb
+
+from employees
+| eval s = scalb(mv_min(salary_change), emp_no / 1000)
+| keep s
+| limit 3;
+
+s:double
+1218.56
+-7403.52
+13127.68
+;
+
+scalbWithEvalAndSort
+required_capability: fn_scalb
+from employees
+| eval s = scalb(mv_min(salary_change), 5)
+| where scalb(mv_max(salary_change), 5) > 100
+| keep s, emp_no, salary, salary_change
+| sort s, emp_no
+| limit 3;
+
+s:double | emp_no:integer | salary:integer | salary_change:double
+-313.92 | 10065 | 50249 | [-9.81, -1.47, 14.44]
+-296.96 | 10042 | 30404 | [-9.28, 9.42]
+-295.36 | 10091 | 38645 | [-9.23, 5.19, 5.85, 7.5]
+
+;
diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbConstantEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbConstantEvaluator.java
new file mode 100644
index 0000000000000..70629305e3e9f
--- /dev/null
+++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbConstantEvaluator.java
@@ -0,0 +1,139 @@
+// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+// or more contributor license agreements. Licensed under the Elastic License
+// 2.0; you may not use this file except in compliance with the Elastic License
+// 2.0.
+package org.elasticsearch.xpack.esql.expression.function.scalar.math;
+
+import java.lang.ArithmeticException;
+import java.lang.IllegalArgumentException;
+import java.lang.Override;
+import java.lang.String;
+import org.elasticsearch.compute.data.Block;
+import org.elasticsearch.compute.data.DoubleBlock;
+import org.elasticsearch.compute.data.DoubleVector;
+import org.elasticsearch.compute.data.Page;
+import org.elasticsearch.compute.operator.DriverContext;
+import org.elasticsearch.compute.operator.EvalOperator;
+import org.elasticsearch.compute.operator.Warnings;
+import org.elasticsearch.core.Releasables;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+
+/**
+ * {@link EvalOperator.ExpressionEvaluator} implementation for {@link Scalb}.
+ * This class is generated. Edit {@code EvaluatorImplementer} instead.
+ */
+public final class ScalbConstantEvaluator implements EvalOperator.ExpressionEvaluator {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator d;
+
+ private final int scaleFactor;
+
+ private final DriverContext driverContext;
+
+ private Warnings warnings;
+
+ public ScalbConstantEvaluator(Source source, EvalOperator.ExpressionEvaluator d, int scaleFactor,
+ DriverContext driverContext) {
+ this.source = source;
+ this.d = d;
+ this.scaleFactor = scaleFactor;
+ this.driverContext = driverContext;
+ }
+
+ @Override
+ public Block eval(Page page) {
+ try (DoubleBlock dBlock = (DoubleBlock) d.eval(page)) {
+ DoubleVector dVector = dBlock.asVector();
+ if (dVector == null) {
+ return eval(page.getPositionCount(), dBlock);
+ }
+ return eval(page.getPositionCount(), dVector);
+ }
+ }
+
+ public DoubleBlock eval(int positionCount, DoubleBlock dBlock) {
+ try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) {
+ position: for (int p = 0; p < positionCount; p++) {
+ if (dBlock.isNull(p)) {
+ result.appendNull();
+ continue position;
+ }
+ if (dBlock.getValueCount(p) != 1) {
+ if (dBlock.getValueCount(p) > 1) {
+ warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value"));
+ }
+ result.appendNull();
+ continue position;
+ }
+ try {
+ result.appendDouble(Scalb.processConstant(dBlock.getDouble(dBlock.getFirstValueIndex(p)), this.scaleFactor));
+ } catch (ArithmeticException e) {
+ warnings().registerException(e);
+ result.appendNull();
+ }
+ }
+ return result.build();
+ }
+ }
+
+ public DoubleBlock eval(int positionCount, DoubleVector dVector) {
+ try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) {
+ position: for (int p = 0; p < positionCount; p++) {
+ try {
+ result.appendDouble(Scalb.processConstant(dVector.getDouble(p), this.scaleFactor));
+ } catch (ArithmeticException e) {
+ warnings().registerException(e);
+ result.appendNull();
+ }
+ }
+ return result.build();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ScalbConstantEvaluator[" + "d=" + d + ", scaleFactor=" + scaleFactor + "]";
+ }
+
+ @Override
+ public void close() {
+ Releasables.closeExpectNoException(d);
+ }
+
+ private Warnings warnings() {
+ if (warnings == null) {
+ this.warnings = Warnings.createWarnings(
+ driverContext.warningsMode(),
+ source.source().getLineNumber(),
+ source.source().getColumnNumber(),
+ source.text()
+ );
+ }
+ return warnings;
+ }
+
+ static class Factory implements EvalOperator.ExpressionEvaluator.Factory {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator.Factory d;
+
+ private final int scaleFactor;
+
+ public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory d, int scaleFactor) {
+ this.source = source;
+ this.d = d;
+ this.scaleFactor = scaleFactor;
+ }
+
+ @Override
+ public ScalbConstantEvaluator get(DriverContext context) {
+ return new ScalbConstantEvaluator(source, d.get(context), scaleFactor, context);
+ }
+
+ @Override
+ public String toString() {
+ return "ScalbConstantEvaluator[" + "d=" + d + ", scaleFactor=" + scaleFactor + "]";
+ }
+ }
+}
diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbIntEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbIntEvaluator.java
new file mode 100644
index 0000000000000..510f736f55a32
--- /dev/null
+++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbIntEvaluator.java
@@ -0,0 +1,159 @@
+// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+// or more contributor license agreements. Licensed under the Elastic License
+// 2.0; you may not use this file except in compliance with the Elastic License
+// 2.0.
+package org.elasticsearch.xpack.esql.expression.function.scalar.math;
+
+import java.lang.ArithmeticException;
+import java.lang.IllegalArgumentException;
+import java.lang.Override;
+import java.lang.String;
+import org.elasticsearch.compute.data.Block;
+import org.elasticsearch.compute.data.DoubleBlock;
+import org.elasticsearch.compute.data.DoubleVector;
+import org.elasticsearch.compute.data.IntBlock;
+import org.elasticsearch.compute.data.IntVector;
+import org.elasticsearch.compute.data.Page;
+import org.elasticsearch.compute.operator.DriverContext;
+import org.elasticsearch.compute.operator.EvalOperator;
+import org.elasticsearch.compute.operator.Warnings;
+import org.elasticsearch.core.Releasables;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+
+/**
+ * {@link EvalOperator.ExpressionEvaluator} implementation for {@link Scalb}.
+ * This class is generated. Edit {@code EvaluatorImplementer} instead.
+ */
+public final class ScalbIntEvaluator implements EvalOperator.ExpressionEvaluator {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator d;
+
+ private final EvalOperator.ExpressionEvaluator scaleFactor;
+
+ private final DriverContext driverContext;
+
+ private Warnings warnings;
+
+ public ScalbIntEvaluator(Source source, EvalOperator.ExpressionEvaluator d,
+ EvalOperator.ExpressionEvaluator scaleFactor, DriverContext driverContext) {
+ this.source = source;
+ this.d = d;
+ this.scaleFactor = scaleFactor;
+ this.driverContext = driverContext;
+ }
+
+ @Override
+ public Block eval(Page page) {
+ try (DoubleBlock dBlock = (DoubleBlock) d.eval(page)) {
+ try (IntBlock scaleFactorBlock = (IntBlock) scaleFactor.eval(page)) {
+ DoubleVector dVector = dBlock.asVector();
+ if (dVector == null) {
+ return eval(page.getPositionCount(), dBlock, scaleFactorBlock);
+ }
+ IntVector scaleFactorVector = scaleFactorBlock.asVector();
+ if (scaleFactorVector == null) {
+ return eval(page.getPositionCount(), dBlock, scaleFactorBlock);
+ }
+ return eval(page.getPositionCount(), dVector, scaleFactorVector);
+ }
+ }
+ }
+
+ public DoubleBlock eval(int positionCount, DoubleBlock dBlock, IntBlock scaleFactorBlock) {
+ try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) {
+ position: for (int p = 0; p < positionCount; p++) {
+ if (dBlock.isNull(p)) {
+ result.appendNull();
+ continue position;
+ }
+ if (dBlock.getValueCount(p) != 1) {
+ if (dBlock.getValueCount(p) > 1) {
+ warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value"));
+ }
+ result.appendNull();
+ continue position;
+ }
+ if (scaleFactorBlock.isNull(p)) {
+ result.appendNull();
+ continue position;
+ }
+ if (scaleFactorBlock.getValueCount(p) != 1) {
+ if (scaleFactorBlock.getValueCount(p) > 1) {
+ warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value"));
+ }
+ result.appendNull();
+ continue position;
+ }
+ try {
+ result.appendDouble(Scalb.process(dBlock.getDouble(dBlock.getFirstValueIndex(p)), scaleFactorBlock.getInt(scaleFactorBlock.getFirstValueIndex(p))));
+ } catch (ArithmeticException e) {
+ warnings().registerException(e);
+ result.appendNull();
+ }
+ }
+ return result.build();
+ }
+ }
+
+ public DoubleBlock eval(int positionCount, DoubleVector dVector, IntVector scaleFactorVector) {
+ try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) {
+ position: for (int p = 0; p < positionCount; p++) {
+ try {
+ result.appendDouble(Scalb.process(dVector.getDouble(p), scaleFactorVector.getInt(p)));
+ } catch (ArithmeticException e) {
+ warnings().registerException(e);
+ result.appendNull();
+ }
+ }
+ return result.build();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ScalbIntEvaluator[" + "d=" + d + ", scaleFactor=" + scaleFactor + "]";
+ }
+
+ @Override
+ public void close() {
+ Releasables.closeExpectNoException(d, scaleFactor);
+ }
+
+ private Warnings warnings() {
+ if (warnings == null) {
+ this.warnings = Warnings.createWarnings(
+ driverContext.warningsMode(),
+ source.source().getLineNumber(),
+ source.source().getColumnNumber(),
+ source.text()
+ );
+ }
+ return warnings;
+ }
+
+ static class Factory implements EvalOperator.ExpressionEvaluator.Factory {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator.Factory d;
+
+ private final EvalOperator.ExpressionEvaluator.Factory scaleFactor;
+
+ public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory d,
+ EvalOperator.ExpressionEvaluator.Factory scaleFactor) {
+ this.source = source;
+ this.d = d;
+ this.scaleFactor = scaleFactor;
+ }
+
+ @Override
+ public ScalbIntEvaluator get(DriverContext context) {
+ return new ScalbIntEvaluator(source, d.get(context), scaleFactor.get(context), context);
+ }
+
+ @Override
+ public String toString() {
+ return "ScalbIntEvaluator[" + "d=" + d + ", scaleFactor=" + scaleFactor + "]";
+ }
+ }
+}
diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbLongEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbLongEvaluator.java
new file mode 100644
index 0000000000000..7e24b28332dc3
--- /dev/null
+++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbLongEvaluator.java
@@ -0,0 +1,159 @@
+// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+// or more contributor license agreements. Licensed under the Elastic License
+// 2.0; you may not use this file except in compliance with the Elastic License
+// 2.0.
+package org.elasticsearch.xpack.esql.expression.function.scalar.math;
+
+import java.lang.ArithmeticException;
+import java.lang.IllegalArgumentException;
+import java.lang.Override;
+import java.lang.String;
+import org.elasticsearch.compute.data.Block;
+import org.elasticsearch.compute.data.DoubleBlock;
+import org.elasticsearch.compute.data.DoubleVector;
+import org.elasticsearch.compute.data.LongBlock;
+import org.elasticsearch.compute.data.LongVector;
+import org.elasticsearch.compute.data.Page;
+import org.elasticsearch.compute.operator.DriverContext;
+import org.elasticsearch.compute.operator.EvalOperator;
+import org.elasticsearch.compute.operator.Warnings;
+import org.elasticsearch.core.Releasables;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+
+/**
+ * {@link EvalOperator.ExpressionEvaluator} implementation for {@link Scalb}.
+ * This class is generated. Edit {@code EvaluatorImplementer} instead.
+ */
+public final class ScalbLongEvaluator implements EvalOperator.ExpressionEvaluator {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator d;
+
+ private final EvalOperator.ExpressionEvaluator scaleFactor;
+
+ private final DriverContext driverContext;
+
+ private Warnings warnings;
+
+ public ScalbLongEvaluator(Source source, EvalOperator.ExpressionEvaluator d,
+ EvalOperator.ExpressionEvaluator scaleFactor, DriverContext driverContext) {
+ this.source = source;
+ this.d = d;
+ this.scaleFactor = scaleFactor;
+ this.driverContext = driverContext;
+ }
+
+ @Override
+ public Block eval(Page page) {
+ try (DoubleBlock dBlock = (DoubleBlock) d.eval(page)) {
+ try (LongBlock scaleFactorBlock = (LongBlock) scaleFactor.eval(page)) {
+ DoubleVector dVector = dBlock.asVector();
+ if (dVector == null) {
+ return eval(page.getPositionCount(), dBlock, scaleFactorBlock);
+ }
+ LongVector scaleFactorVector = scaleFactorBlock.asVector();
+ if (scaleFactorVector == null) {
+ return eval(page.getPositionCount(), dBlock, scaleFactorBlock);
+ }
+ return eval(page.getPositionCount(), dVector, scaleFactorVector);
+ }
+ }
+ }
+
+ public DoubleBlock eval(int positionCount, DoubleBlock dBlock, LongBlock scaleFactorBlock) {
+ try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) {
+ position: for (int p = 0; p < positionCount; p++) {
+ if (dBlock.isNull(p)) {
+ result.appendNull();
+ continue position;
+ }
+ if (dBlock.getValueCount(p) != 1) {
+ if (dBlock.getValueCount(p) > 1) {
+ warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value"));
+ }
+ result.appendNull();
+ continue position;
+ }
+ if (scaleFactorBlock.isNull(p)) {
+ result.appendNull();
+ continue position;
+ }
+ if (scaleFactorBlock.getValueCount(p) != 1) {
+ if (scaleFactorBlock.getValueCount(p) > 1) {
+ warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value"));
+ }
+ result.appendNull();
+ continue position;
+ }
+ try {
+ result.appendDouble(Scalb.process(dBlock.getDouble(dBlock.getFirstValueIndex(p)), scaleFactorBlock.getLong(scaleFactorBlock.getFirstValueIndex(p))));
+ } catch (ArithmeticException e) {
+ warnings().registerException(e);
+ result.appendNull();
+ }
+ }
+ return result.build();
+ }
+ }
+
+ public DoubleBlock eval(int positionCount, DoubleVector dVector, LongVector scaleFactorVector) {
+ try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) {
+ position: for (int p = 0; p < positionCount; p++) {
+ try {
+ result.appendDouble(Scalb.process(dVector.getDouble(p), scaleFactorVector.getLong(p)));
+ } catch (ArithmeticException e) {
+ warnings().registerException(e);
+ result.appendNull();
+ }
+ }
+ return result.build();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ScalbLongEvaluator[" + "d=" + d + ", scaleFactor=" + scaleFactor + "]";
+ }
+
+ @Override
+ public void close() {
+ Releasables.closeExpectNoException(d, scaleFactor);
+ }
+
+ private Warnings warnings() {
+ if (warnings == null) {
+ this.warnings = Warnings.createWarnings(
+ driverContext.warningsMode(),
+ source.source().getLineNumber(),
+ source.source().getColumnNumber(),
+ source.text()
+ );
+ }
+ return warnings;
+ }
+
+ static class Factory implements EvalOperator.ExpressionEvaluator.Factory {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator.Factory d;
+
+ private final EvalOperator.ExpressionEvaluator.Factory scaleFactor;
+
+ public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory d,
+ EvalOperator.ExpressionEvaluator.Factory scaleFactor) {
+ this.source = source;
+ this.d = d;
+ this.scaleFactor = scaleFactor;
+ }
+
+ @Override
+ public ScalbLongEvaluator get(DriverContext context) {
+ return new ScalbLongEvaluator(source, d.get(context), scaleFactor.get(context), context);
+ }
+
+ @Override
+ public String toString() {
+ return "ScalbLongEvaluator[" + "d=" + d + ", scaleFactor=" + scaleFactor + "]";
+ }
+ }
+}
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
index 07990a72e99cc..133cd55312ead 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
@@ -213,6 +213,11 @@ public enum Cap {
*/
FN_ROUND_UL_FIXES,
+ /**
+ * Support for function {@code SCALB}.
+ */
+ FN_SCALB,
+
/**
* Fixes for multiple functions not serializing their source, and emitting warnings with wrong line number and text.
*/
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java
index 4ffa778b8287a..7e17d25f67e72 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java
@@ -45,6 +45,7 @@
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Exp;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Floor;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Log10;
+import org.elasticsearch.xpack.esql.expression.function.scalar.math.Scalb;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Signum;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Sin;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Sinh;
@@ -169,6 +170,7 @@ public static List unaryScalars() {
entries.add(Not.ENTRY);
entries.add(RLike.ENTRY);
entries.add(RTrim.ENTRY);
+ entries.add(Scalb.ENTRY);
entries.add(Signum.ENTRY);
entries.add(Sin.ENTRY);
entries.add(Sinh.ENTRY);
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
index b2d85d809c058..67d11197efe5e 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
@@ -100,6 +100,7 @@
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Pi;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Pow;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Round;
+import org.elasticsearch.xpack.esql.expression.function.scalar.math.Scalb;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Signum;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Sin;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Sinh;
@@ -325,6 +326,7 @@ private static FunctionDefinition[][] functions() {
def(Pi.class, Pi::new, "pi"),
def(Pow.class, Pow::new, "pow"),
def(Round.class, Round::new, "round"),
+ def(Scalb.class, Scalb::new, "scalb"),
def(Signum.class, Signum::new, "signum"),
def(Sin.class, Sin::new, "sin"),
def(Sinh.class, Sinh::new, "sinh"),
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Round.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Round.java
index 7c977fd1ce5a6..70882573b518d 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Round.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Round.java
@@ -47,7 +47,6 @@ public class Round extends EsqlScalarFunction implements OptionalArgument {
private final Expression field, decimals;
- // @TODO: add support for "integer", "long", "unsigned_long" once tests are fixed
@FunctionInfo(returnType = { "double", "integer", "long", "unsigned_long" }, description = """
Rounds a number to the specified number of decimal places.
Defaults to 0, which returns the nearest integer. If the
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Scalb.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Scalb.java
new file mode 100644
index 0000000000000..fea0e284b5545
--- /dev/null
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Scalb.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.expression.function.scalar.math;
+
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.compute.ann.Evaluator;
+import org.elasticsearch.compute.ann.Fixed;
+import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.core.util.NumericUtils;
+import org.elasticsearch.xpack.esql.expression.function.Example;
+import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
+import org.elasticsearch.xpack.esql.expression.function.Param;
+import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction;
+import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST;
+import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND;
+import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNumeric;
+
+public class Scalb extends EsqlScalarFunction {
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Scalb", Scalb::new);
+
+ private final Expression d;
+ private final Expression scaleFactor;
+
+ @FunctionInfo(
+ returnType = "double",
+ description = "Returns the result of `d * 2 ^ scaleFactor`.",
+ note = "Similar to Java's `scalb` function. Result is rounded as if performed by a single "
+ + "correctly rounded floating-point multiply to a member of the double value set.",
+ examples = { @Example(file = "math", tag = "ScalbDI"), @Example(file = "math", tag = "ScalbID-sqrt", description = """
+ The scaleFactor can be a fraction, which is similar to performing a root.
+ For example, the scaleFactor of `0.5` will give the square root of the d:"""), }
+ )
+ public Scalb(
+ Source source,
+ @Param(
+ name = "d",
+ type = { "double", "integer", "long", "unsigned_long" },
+ description = "Numeric expression for the multiplier. If `null`, the function returns `null`."
+ ) Expression d,
+ @Param(
+ name = "scaleFactor",
+ type = { "integer", "long" },
+ description = "Numeric expression for the scale factor. If `null`, the function returns `null`."
+ ) Expression scaleFactor
+ ) {
+ super(source, Arrays.asList(d, scaleFactor));
+ this.d = d;
+ this.scaleFactor = scaleFactor;
+ }
+
+ private Scalb(StreamInput in) throws IOException {
+ this(Source.readFrom((PlanStreamInput) in), in.readNamedWriteable(Expression.class), in.readNamedWriteable(Expression.class));
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ source().writeTo(out);
+ out.writeNamedWriteable(d);
+ out.writeNamedWriteable(scaleFactor);
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
+ }
+
+ @Override
+ protected TypeResolution resolveType() {
+ if (childrenResolved() == false) {
+ return new TypeResolution("Unresolved children");
+ }
+
+ TypeResolution resolution = isNumeric(d, sourceText(), FIRST);
+ if (resolution.unresolved()) {
+ return resolution;
+ }
+
+ return isNumeric(scaleFactor, sourceText(), SECOND);
+ }
+
+ @Override
+ public boolean foldable() {
+ return d.foldable() && scaleFactor.foldable();
+ }
+
+ @Evaluator(extraName = "Int", warnExceptions = { ArithmeticException.class })
+ static double process(double d, int scaleFactor) {
+ return NumericUtils.asFiniteNumber(Math.scalb(d, scaleFactor));
+ }
+
+ @Evaluator(extraName = "Long", warnExceptions = { ArithmeticException.class })
+ static double process(double d, long scaleFactor) {
+ return NumericUtils.asFiniteNumber(Math.scalb(d, Math.toIntExact(scaleFactor)));
+ }
+
+ @Evaluator(extraName = "Constant", warnExceptions = { ArithmeticException.class })
+ static double processConstant(double d, @Fixed int scaleFactor) {
+ return NumericUtils.asFiniteNumber(Math.scalb(d, scaleFactor));
+ }
+
+ @Override
+ public final Expression replaceChildren(List newChildren) {
+ return new Scalb(source(), newChildren.get(0), newChildren.get(1));
+ }
+
+ @Override
+ protected NodeInfo extends Expression> info() {
+ return NodeInfo.create(this, Scalb::new, d(), scaleFactor());
+ }
+
+ public Expression d() {
+ return d;
+ }
+
+ public Expression scaleFactor() {
+ return scaleFactor;
+ }
+
+ @Override
+ public DataType dataType() {
+ return DataType.DOUBLE;
+ }
+
+ @Override
+ public ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) {
+ var dEval = Cast.cast(source(), d.dataType(), DataType.DOUBLE, toEvaluator.apply(d));
+ if (scaleFactor.foldable()) {
+ Integer factor = switch (scaleFactor.dataType()) {
+ case DataType.INTEGER -> (Integer) scaleFactor.fold(toEvaluator.foldCtx());
+ case DataType.LONG -> Math.toIntExact((Long) scaleFactor.fold(toEvaluator.foldCtx()));
+ default -> throw new IllegalStateException("Invalid type for scaleFactor, should be int or long.");
+ };
+ return new ScalbConstantEvaluator.Factory(source(), dEval, factor);
+ }
+ var scaleFactorEval = toEvaluator.apply(scaleFactor);
+ return switch (scaleFactor.dataType()) {
+ case DataType.INTEGER -> new ScalbIntEvaluator.Factory(source(), dEval, scaleFactorEval);
+ case DataType.LONG -> new ScalbLongEvaluator.Factory(source(), dEval, scaleFactorEval);
+ case DataType type -> throw new IllegalStateException(
+ String.format("Invalid type for scaleFactor: %s, should be int or long.", type)
+ );
+ };
+
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbTests.java
new file mode 100644
index 0000000000000..5ae4946a22c18
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbTests.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.expression.function.scalar.math;
+
+import com.carrotsearch.randomizedtesting.annotations.Name;
+import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
+
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class ScalbTests extends AbstractScalarFunctionTestCase {
+ public ScalbTests(@Name("TestCase") Supplier testCaseSupplier) {
+ this.testCase = testCaseSupplier.get();
+ }
+
+ @ParametersFactory
+ public static Iterable