MathUtil supplies the inverse hyperbolic functions that java.lang.Math lacks. It uses the schoolbook formulas, which break at both ends of the double range.
asinh,acoshandatanhundosinh,coshandtanh.java.lang.Mathhassinh,coshandtanhbut no inverses, so Lucene'sMathUtilprovides them, and the expressions module offers them as the default functionsasinh,acoshandatanh.- They grow very slowly:
asinh(1e200)is about 461, and even at the largest double, about 1.8e308, asinh is only about 710.48.acoshis defined only from 1 up; below 1 the answer is NaN. - A double cannot hold anything above about 1.8e308; a bigger result becomes Infinity. So squaring any number of 2^512 (about 1.34e154) or more gives Infinity.
- Near 1, doubles are about 2.2e-16 apart, so adding a number below about 1.1e-16 to 1 gives exactly 1.
MathUtil.asinh(1e200) ends up as Infinity: the intermediate square 1e200 * 1e200 overflows, although the true result is 461.21. Select the still to open it at full size. Generated with AI, checked against the code.- You call
MathUtil.asinh(1e200), or sort documents by the expressionasinh(x)where a document'sxis 1e200. - You expect 461.2101657793691, that is ln(2e200). The inverse hyperbolic sine of any finite double is finite: at most 710.48, at
Double.MAX_VALUE. - You get Infinity. The code computes
log(sqrt(a*a + 1) + a), anda*aoverflows for every |a| of 2^512 (about 1.34e154) or more: just below 2^512 asinh is 355.58, at 2^512 it is Infinity.acosh(1e200)overflows the same way. - At the small end, the log's argument
sqrt(a*a + 1) + ais1 + a, and1 + arounds to exactly 1, soasinh(1e-17)andatanh(1e-17)return 0.0 instead of 1e-17. And acosh never applies its documented rule "less than 1 gives NaN":acosh(-1e8)is -Infinity,acosh(-1e200)is Infinity. - No error. Sorting by
asinh(x)gives x = 1e160 and x = 1e200 the same key, Infinity, and_score + 0.000001 * asinh(x)ranks a document with x = 1e200 above better text matches. Ordinary arguments such asasinh(2.5)are correct.
Direct callers of MathUtil.asinh, acosh or atanh, and users of the optional expressions module whose expressions apply them to literals, doc values or _score. It needs magnitudes of 1.34e154 or more, below 1.1e-16, or acosh of about -6.7e7 or less: plausible paths are sentinels such as Double.MAX_VALUE and malformed or extreme data. An accidental hit is unlikely; low priority.
Mechanism, terms and numbers701 words
MathUtil.asinh, acosh and atanh evaluate the textbook formulas log(sqrt(a*a + 1) + a), log(sqrt(a*a - 1) + a) and 0.5 * log((1 + a) / (1 - a)) with no range reduction and no domain check. For every finite |a| of 2^512 (about 1.34e154) or more, a*a overflows, so asinh(1e200) and acosh(1e200) return Infinity instead of 461.2101657793691. For tiny arguments the log's argument (1 + a for asinh, (1 + a) / (1 - a) for atanh) rounds to exactly 1, so asinh(1e-17) and atanh(1e-17) return 0.0 instead of 1e-17, and just above that range asinh keeps only about 8 correct digits at 1e-8. acosh never checks its documented rule "If the argument is less than 1, then the result is NaN": acosh(-1e8) returns -Infinity and acosh(-1e200) Infinity. The default expression functions asinh, acosh and atanh are bound straight to these methods, so expression scores, sort keys and filters come out wrong with no error; outside tests, no other Lucene code calls them. Every release since 4.6.0 (2013) is affected; the inputs are extreme (plausible paths are sentinels and malformed or extreme data), so the realistic exposure looks low. A range-reduced implementation with an explicit NaN for acosh below 1 fixes all of it.
- asinh (
MathUtil.java:102-113) strips the sign and returnssign * Math.log(Math.sqrt(a * a + 1.0d) + a)(:112). For every finitea >= 2^512,a * ais +Infinity, andsqrtandlogkeep it: the result is Infinity (-Infinity for a negative argument). For0 < a <= 2^-53, the log's argumentMath.sqrt(a * a + 1.0d) + ais1.0 + a, which rounds to 1, andlog(1)is 0. (1 + a * ais already exactly 1 for everyaup to about 1.05e-8, where asinh is still nonzero, so the value is lost at1 + a.) Both thresholds are exact: SMT over IEEE binary64 (cvc5 and z3, QF_FP) proves that the log argument is Infinity for every finitea >= 2^512and not infinite on[1, 2^512), and that it is exactly 1 on(0, 2^-53]and not 1 above. Just above the small threshold the relative error is about1.1e-16 / a: 6.7e7 ulp at 1e-8, 237 ulp at 1e-3. - acosh (
:127-129) is one line,return Math.log(Math.sqrt(a * a - 1.0d) + a);(:128), with no check of its documented domain (:124). Large positive arguments overflow like asinh. Negative arguments reach NaN only by accident (for -10,sqrt(99) - 10is negative, andlogof a negative number is NaN). From about -6.7e7 (-2^26) down, rounding makessqrt(a * a - 1) + acome out 0 or negative (from about -1.34e8 on,a * a - 1rounds toa * a): between about -1.34e8 and -6.7e7 the result is -Infinity for some arguments and NaN for others, from about -1.34e8 (-2^27) to -2^512 it is -Infinity, and from -2^512 downa * aoverflows and the result is Infinity. No negative argument gives a finite result. - atanh (
:144-154) returnsmult * Math.log((1.0d + a) / (1.0d - a))(:153). For0 < |a| <= 2^-54(about 5.6e-17) both1 + aand1 - around to 1, so the result is 0. There is no overflow case (the domain is|a| <= 1). - Expressions.
JavascriptCompiler.properties:23, :25, :28bindacosh,asinhandatanhto these methods (whilecosh,sinhandtanhgo tojava.lang.Math,:31, :43, :46);compile(String)uses this default function set (JavascriptCompiler.java:154-155), and the package javadoc lists them (js/package-info.java:31-32). Outside tests, nothing else in Lucene calls them.
| call | correct (correctly rounded) | main |
|---|---|---|
asinh(1e200), acosh(1e200), expression asinh(1e200) | 461.2101657793691 | Infinity |
asinh(nextDown(2^512)) / asinh(2^512) | 355.58450362725193 (both) | 355.58450362725193 / Infinity |
asinh(Double.MAX_VALUE) | about 710.476 | Infinity |
asinh(1e-17), atanh(1e-17) | 1.0E-17 | 0.0 |
asinh(1e-8) | 1.0E-8 | 9.999999889225291E-9 |
acosh(-1e8), expression acosh(-1e8) / acosh(-1e200) | NaN | -Infinity / Infinity |
controls: asinh(2.5), acosh(2.5), atanh(0.5), acosh(-10), acosh(-1e7) | 1.6472311463710958, 1.566799236972411, 0.5493061443340549, NaN, NaN | the same, except atanh(0.5) = 0.5493061443340548 (one ulp low; the fix gives the same) |
Exposure and likelihood321 words
- Who. Direct callers of the public static
MathUtil.asinh/acosh/atanh(public final class MathUtil, no@lucene.internal), and users of the optional expressions module whose expressions callasinh,acoshoratanhwith the default function set over literals or bound values such as doc values or_score. Outside tests, no Lucene code calls them. - Sort ties. Sorting by the expression
asinh(x)over aDoubleDocValuesFieldgives the keys346.0809111296668 Infinity Infinityfor x = 1e150, 1e160 and 1e200, so two distinct documents tie although asinh is strictly increasing (correct: 346.08, 369.11, 461.21). Executed on main with assertions on and off, and on 10.5.2. - Scores. With
FunctionScoreQueryand_score + 0.000001 * asinh(x)over a term query, the documents with x = 1e200 and 1e160 score Infinity and rank first and second, ahead of better text matches scoring 0.0498 and 0.0486; with correct math they score 0.0310 and 0.0309. Correct math caps the term at 710.48 times its weight. - Filters.
FunctionMatchQuerywithasinh(x) > 400also matches x = 1e160 (true asinh 369.1),acosh(x) > 1matches x = -1e200 (true NaN), andasinh(x) == 0matches x = 1e-17. - Scope of these examples. The sort, score and filter results come from constructed public-API tests that show what can happen; no production impact was observed.
- What it takes. Magnitudes of at least 1.34e154 (asinh, acosh), at most 1.1e-16 (asinh) or 5.6e-17 (atanh), or acosh of about -6.7e7 or less. These ranges look contrived for typical ranking inputs; sentinels and malformed or extreme data are plausible paths. The tiny-argument zero changes a result by at most about 1.11e-16 (asinh) or 5.6e-17 (atanh). No public report was found in the recorded searches. Registry likelihood contrived, priority low.
- Not affected. Nothing is written to the index; there is no exception or hang, and no sign of resource exhaustion (30 million evaluations on collapse inputs took 111 ms in one local run; not a performance claim).
One JUnit class, one method, one assertion, public API only: MathUtil.asinh(1e200) must be about 461.21. Pure arithmetic, so the seed does not matter; it fails with assertions on and off.
/*
* 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.lucene.util;
import org.apache.lucene.tests.util.LuceneTestCase;
public class TestMathUtilAsinhLargeArgument extends LuceneTestCase {
public void testAsinhOfLargeFiniteArgument() {
// expected: ln(2e200) ~ 461.21; actual: Infinity, because 1e200 * 1e200 overflows
assertEquals(461.2101657793691, MathUtil.asinh(1e200), 1e-9);
}
}
cd $LUCENE && ./gradlew :lucene:core:test --tests org.apache.lucene.util.TestMathUtilAsinhLargeArgument -Ptests.seed=DEADBEEF
- Expected
MathUtil.asinh(1e200)returns 461.2101657793691, that is ln(2e200).- Observed
expected:<461.2101657793691> but was:<Infinity>: the intermediate1e200 * 1e200overflowed.
More files, environment, full output217 words
The class sits in org.apache.lucene.util only to be next to TestMathUtil. It compiles on 9.1 and later (org.apache.lucene.tests.util.LuceneTestCase); on 9.0 and older the base class is org.apache.lucene.util.LuceneTestCase.
It shows the overflow only. The other symptoms (acosh overflow, the tiny-argument zeros, acosh of large negative arguments) are the executed values in the table above; a NaN assertion for acosh (for example assertTrue(Double.isNaN(MathUtil.acosh(-1e8))), not run as a test) would be a second test method with its own fix hunk.
Regression test: this same test, as written.
| lucene | main 645d4fe14a20 (11.0.0-SNAPSHOT): fails; 10.6.0, 9.12.3: fail; with the recommended fix: passes |
|---|---|
| jdk | OpenJDK 25.0.3 (build 25.0.3+9-LTS) |
| os | Linux, aarch64 (the only platform run; IEEE double arithmetic) |
cp $REPRO/TestMathUtilAsinhLargeArgument.java $LUCENE/lucene/core/src/test/org/apache/lucene/util/
cd $LUCENE && ./gradlew :lucene:core:test --tests org.apache.lucene.util.TestMathUtilAsinhLargeArgument -Ptests.seed=DEADBEEF
# assertions off fails the same way: add -Ptests.asserts=false (the recorded runs added --offline --no-daemon)
# gradle, scratch worktree of 645d4fe14a20, seed DEADBEEF, assertions on
TestMathUtilAsinhLargeArgument > testAsinhOfLargeFiniteArgument FAILED
java.lang.AssertionError: expected:<461.2101657793691> but was:<Infinity>
at __randomizedtesting.SeedInfo.seed([DEADBEEF:667FD9FA2387ECB5]:0)
at org.junit.Assert.fail(Assert.java:89)
at org.junit.Assert.failNotEquals(Assert.java:835)
at org.junit.Assert.assertEquals(Assert.java:555)
at org.junit.Assert.assertEquals(Assert.java:685)
at org.apache.lucene.util.TestMathUtilAsinhLargeArgument.testAsinhOfLargeFiniteArgument(TestMathUtilAsinhLargeArgument.java:25)
# assertions off (-Ptests.asserts=false): the same message
# standalone runner, seed DEADBEEF: 1 of 1 failed, same message, on main 645d4fe14a20 (-ea and -da), 10.6.0, 9.12.3
# with the recommended fix: the test passes (assertions on and off)- The javadoc says the methods calculate the inverse hyperbolic sine, cosine and tangent; those are finite for every finite argument, yet
asinh(1e200)is Infinity. - The acosh javadoc says "If the argument is less than 1, then the result is NaN";
acosh(-1e8)is -Infinity. - The 2013 formulas were never revised; no test or discussion limits their range.
The full argument236 words
- The contract. "Calculates inverse hyperbolic sine / cosine / tangent of a
doublevalue" (MathUtil.java:91,:116,:132), followed by special cases: an infinite result for an infinite argument (asinh), for +Infinity (acosh) or for +-1 (atanh), and a zero for a zero argument (asinh, atanh) or for +1 (acosh) (:93-100,:118-125,:134-142). The true values here are finite and nonzero, so Infinity at 1e200 or 0 at 1e-17 is the loss of the whole value, not rounding. (The list has no "only"; the argument rests on what the functions are.) - The acosh rule. "If the argument is less than 1, then the result is NaN" (
:124).TestMathUtil.testAcoshMethodasserts NaN for 0.9, 0, -0, -0.9, -1, -10 and -Infinity (TestMathUtil.java:105-121). - Monotonicity.
asinh(nextDown(2^512))is 355.58450362725193; the next double up gives Infinity. - History. The formula lines blame to
c73f175c71ee(LUCENE-5207, 2013) at645d4fe14a20; the migrated LUCENE-5207 discussion (#6271, 89 JIRA comments) never discusses the range or accuracy of these functions. The upstream tests stop at|x| = 1234567.89(TestMathUtil.java:123-126,:146-153,:177-182;TestJavascriptFunction.java:54-57,:74-81,:108-113), so nothing pins the current behaviour. - Precedent. In LUCENE-4162 (#5234, 2012), about another public
MathUtilmethod, Uwe Schindler wrote: "Otherwise this class must be hidden, we should not offer any public method to the users that can loop infinite without parameter checking." It was fixed.
The devil's-advocate check207 words
The strongest case for "intended" (a devil's-advocate verifier, the cross-model reviews and the adjudication): the util package javadoc says "APIs in this package are primarily internal and may change between releases" (util/package-info.java:35); JavascriptCompiler is @lucene.experimental (JavascriptCompiler.java:111); no ulp bound is promised, unlike java.lang.Math's functions; the upstream tests accept an absolute error of 1e-6 (1e-7 in TestJavascriptFunction) at moderate magnitudes; and the code does exactly what its author wrote.
Why it fails: the first two are about API stability, not permission to return wrong values. The package sentence was added on 2026-02-18 by a docs-only change (#15719), MathUtil itself has no @lucene.internal, and the expression functions are documented for users. No tolerance accepts Infinity for a finite argument, and the acosh NaN rule is explicit. Where the defence is moderate: the tiny-argument zero (an absolute error of at most about 1.11e-16, inside the tests' tolerance) and the graded precision loss above it; for those, a fix is an accuracy improvement rather than a bug fix. The independent reproducer, the devil's-advocate verifier and the intended-lens review said real; the refute-lens review's "not real" was procedural (it could not run code and agreed with the root cause); the adjudication confirmed with high confidence.
Recommended Range reduction, log1p near 0, and NaN for acosh below 1
Use fdlibm-style range reduction: log(|a|) + ln 2 above 2^28, log1p forms near 0 (and near 1 for acosh), and an explicit NaN for acosh below 1 (+29/-3 lines). Validated: the repro passes with assertions on and off, and so do TestMathUtil, the whole expressions module and the core format, ECJ and forbidden-apis checks.
// asinh, after the sign is stripped
final double r;
if (a > 0x1p28) {
r = Math.log(a) + LN2; // a * a may overflow
} else if (a > 2) {
r = Math.log(2 * a + 1 / (Math.sqrt(a * a + 1) + a));
} else {
final double t = a * a; // log1p avoids log(1 + tiny)
r = Math.log1p(a + t / (1 + Math.sqrt(1 + t)));
}
return sign * r;
// acosh: if (!(a >= 1)) return Double.NaN; then the same three ranges
// atanh: return mult * Math.log1p(2 * a / (1 - a));All options and trade-offs3 options · 608 words
The recommended fix was validated through gradle in a scratch worktree of 645d4fe14a20; the smaller guard-only option and documenting the limits were not built.
For large arguments use log(a) + ln 2 (above 2^28, sqrt(a * a +- 1) equals a to double precision); between 2 and 2^28 an equivalent form without overflow; near 0 (asinh, atanh) and near 1 (acosh) Math.log1p forms that avoid the cancellation; acosh returns NaN for any argument below 1 or NaN. The same reductions as fdlibm.
// asinh, after the sign is stripped
final double r;
if (a > 0x1p28) {
r = Math.log(a) + LN2; // a * a may overflow
} else if (a > 2) {
r = Math.log(2 * a + 1 / (Math.sqrt(a * a + 1) + a));
} else {
final double t = a * a; // log1p avoids log(1 + tiny)
r = Math.log1p(a + t / (1 + Math.sqrt(1 + t)));
}
return sign * r;
// acosh: if (!(a >= 1)) return Double.NaN; then the same three ranges
// atanh: return mult * Math.log1p(2 * a / (1 - a));Trade-offs
- Last-bit changes. The formulas are rewritten for ordinary arguments too, so results can move by an ulp and an expression score with them; the upstream tests (absolute tolerance) still pass.
- Cost. One or two extra comparisons, and
Math.log1pinstead ofMath.logfor|a| <= 2(asinh, acosh) and in every atanh call. Not benchmarked. - Accuracy. Maximum error 1.04 ulp (asinh), 0.98 ulp (acosh) and 1.19 ulp (atanh) over 6,898 points covering every binade, against a 200-digit reference (a sample, not a proof of a global error bound), with 0 collapses (stock: 5,028, 1,682 and 3,342 collapses on the same points). fdlibm's split for atanh below 0.5 (
0.5 * log1p(2a + 2a*a / (1 - a))) could bring atanh under 1 ulp; not built. - Design support. Elasticsearch's own 2026 implementations of these functions use the same reductions.
What the checks validated
- Gradle, scratch worktree of
645d4fe14a20, OpenJDK 25.0.3, seed DEADBEEF (2026-10-31). Stock: the repro fails with assertions on and off. With the fix: it passes with assertions on and off. - The repro plus
TestMathUtil(its acosh test asserts the NaN cases): 7 tests, 0 failures.:lucene:expressions:test, the whole module includingTestJavascriptFunction: 13 suites, 170 tests, 0 failures. :lucene:corecheckGoogleJavaFormat,ecjLintMain,ecjLintTest(with the repro test in place),forbiddenApisMain,forbiddenApisTest: pass. The finder's original comment on the atanh line was 101 columns and failed the format check, so the diff shortens it; the code is unchanged.- Earlier: the finder's 7 tests, an independent reproducer's 12 tests (including the acosh NaN cases) and
TestMathUtilplusTestJavascriptFunction(35 tests) pass with it; the value probe gives the correct value for every row of the table above.
if (a > 0x1p28) return sign * (Math.log(a) + LN2); in asinh (the same without the sign in acosh), and if (!(a >= 1)) return Double.NaN; first in acosh; the formulas stay as they are otherwise.
Trade-offs
Fixes the Infinity collapse and the documented NaN rule. It leaves every asinh result for |a| <= 2^28 and every acosh result for 1 <= a <= 2^28 bit-for-bit unchanged (below 1, only the wrong acosh results change, to NaN), but keeps the tiny-argument zeros and the graded precision loss.
State in the javadoc that the functions are accurate only for moderate arguments.
Trade-offs
Leaves Infinity for finite input, and contradicts the acosh special case unless that line changes too.
An application that binds untrusted numeric values into an expression using asinh or acosh lets a finite value of 1.34e154 or more score Infinity and outrank every real match; validation that rejects only Infinity and NaN does not stop it.
Semi-formal. A hunt unit reviewing numeric utilities read the three formulas and suspected overflow and cancellation; SMT solving over exact IEEE double semantics then proved the exact thresholds, and a public-API JUnit on the stock jars confirmed them. An independent reproducer added the acosh NaN violation.
The method in full171 words
- Code reading first. The hunt unit for numeric, string and version utilities chose the
MathUtilmethods an earlier study had not covered and read the formulas. - SMT. cvc5 1.4.2 and z3 4.12.2, QF_FP (exact IEEE binary64 semantics with round-to-nearest-even): 6 UNSAT queries prove the exact collapse regions over all doubles (the log argument is +Infinity for every finite
a >= 2^512and not infinite on[1, 2^512); it is exactly 1 for everyain(0, 2^-53]for asinh and(0, 2^-54]for atanh, and not 1 above), with 3 SAT vacuity probes; both solvers agree. - Measurement. An error table of the stock jar over 6,898 points covering every binade, against a 200-digit decimal reference.
- JUnit. Public-API tests on the stock main jars, direct and through
JavascriptCompiler: 7 of 7 fail with assertions on and off. - The acosh negative-argument symptom was found by the independent reproducer, who wrote their own tests from the claim only. So the label is semi-formal: the hypothesis came from reading, the solvers bounded it.
No public report found: 88 logged queries on 2026-10-26 over apache/lucene issues and PRs (the migrated JIRA archive included), open PRs, JIRA, the mailing lists, downstream projects and the web. The bare terms asinh, acosh and atanh match no apache/lucene issue or PR; a recheck on 2026-10-31 found none either.
Searches5 searches
No upstream report or fix was found (searches cannot prove that none exists). The nearest items are the change that introduced the code, a 2012 fix of another MathUtil method, a speed study of the .NET port, and Elasticsearch's own range-reduced functions, none of which mentions this behaviour of Lucene's MathUtil.
| Where | When | How | Found |
|---|---|---|---|
| GitHub issues and PRs (apache/lucene, migrated JIRA included) | 2026-10-26 | asinh, acosh, atanh alone; hyperbolic, "inverse hyperbolic", MathUtil, MathUtil.java, titles with MathUtil or JavascriptCompiler, log1p, fdlibm, StrictMath, expressions with Infinity, NaN or precision, symptom phrases | 0 hits for asinh, acosh and atanh; no relevant hit for the rest |
| Open PRs and commits | 2026-10-26 | changed files of all 411 open PRs; GitHub commit search; local history with git log -S on the three formula strings | no open PR touches MathUtil.java or the function bindings; the formulas were introduced once (2013) and never changed |
| JIRA, mailing lists, downstream projects, web | 2026-10-26 | LUCENE, SOLR and LUCENENET JIRA; dev@, java-user@, issues@ and commits@ (Lucene), the Lucene.NET and Solr lists; Lucene.NET, Elasticsearch, OpenSearch and Solr on GitHub; 12 web queries | no report; apache/lucenenet#297 measured only the speed of the ported functions; Elasticsearch #141128, #142184 and #142213 implement their own functions and never mention Lucene's MathUtil |
| Recheck (GitHub API) | 2026-10-31 | asinh, acosh, atanh, "MathUtil hyperbolic", "inverse hyperbolic", "asinh Infinity", "acosh NaN" in apache/lucene issues and PRs | 0 hits each |
| Closest related items (read) | 2026-10-26 | LUCENE-5207 / #6271 (89 JIRA comments), LUCENE-4162 / #5234 | #6271 introduced the code and never discusses range or accuracy; #5234 fixed the argument check of another MathUtil method |
Introduced in 4.6.0 by LUCENE-5207 (c73f175c71ee, 2013), which added the expressions module and these three methods; the formulas never changed. Every release from 4.6.0 to 10.6.0 and main is affected, and it is not fixed upstream.
Versions tested6 rows
Introduced by c73f175c71ee (2013-09-13, "LUCENE-5207: initial patch from ryan", committed by Robert Muir, patch by Ryan Ernst), on the SVN feature branch that added the expressions module; it landed on 2013-09-15 as eecd78f37a12 on trunk (the lineage of 6.0.0 and later) and as 08df432f8ba2 on branch_4x (4.6.0 to 5.5.5), with identical MathUtil.java. SVN era, no pull request. Not a regression: the values were wrong from the start.
Upstream (2026-10-31): main 2f424fd428da, branch_10_6 and branch_9_12 still contain the three formula lines; the last commit on MathUtil.java in main (#14750, 2025-06-03) is already in 645d4fe14a20. branch_10x was decommissioned on 2026-10-29 (#16911).
| Version or commit | Verdict | Result |
|---|---|---|
main 645d4fe14a20 | affected | the minimal test fails (assertions on and off, gradle and the standalone runner); every wrong value of the table reproduced |
| 10.6.0 | affected | the minimal test fails; the value probe gives identical values to main |
| 9.12.3, 10.5.2 | affected | the minimal test fails on 9.12.3; an independent 12-test JUnit fails 10 of 12 on both (assertions on and off); the acosh negative-argument program fails on both |
| 4.6.0, 4.10.4, 5.5.5, 6.6.6, 7.7.3, 8.11.4 | affected | the version-neutral program gives the same four wrong values; on 4.6.0 also through expressions and for acosh of negative arguments |
| 4.5.1 | API absent | no MathUtil.asinh/acosh/atanh and no expressions module (compile error) |
fix (scratch worktree of 645d4fe14a20) | not affected | the minimal test passes; TestMathUtil and the expressions module pass |
An independent reproducer, a devil's-advocate verifier and a cross-model review said real; the adjudication confirmed with high confidence. A second cross-model review returned a procedural "not real" because, reading only, it could not run code, while agreeing with the root cause. A credibility audit lowered the likelihood to contrived.
Checks and verdicts361 words
Every executing check ran on stock builds of main (ea57360912ed, a9779ef3c193 or 645d4fe14a20, with identical files involved) or on releases.
| Check | Verdict | What it found |
|---|---|---|
| Finder (SMT, error table, JUnit) | candidate | SMT thresholds (6 UNSAT, 3 SAT probes, two solvers); 6,898-point error table; JUnit 7 of 7 fail on main with assertions on and off; a version-neutral program fails on 4.10.4 to 10.5.2; the fix passes 7 of 7 and 35 upstream tests. |
| Cross-model review, intended lens (OpenAI GPT-5.6, reading only) | real (medium) | Rated the intended-behaviour case moderate and rebutted it; a separate run re-ran the finder's test: 7 of 7 fail. |
| Independent reproducer | real (high) | Own 12-test JUnit from the claim only, a 150-digit reference and two controls: 10 of 12 fail on main, 10.5.2 and 9.12.3 with assertions on and off. Found the acosh negative-argument case and confirmed the thresholds; the fix passes 12 of 12. |
| Cross-model review, refute lens (OpenAI GPT-5.6, reading only) | not real (low), procedural | Could not run any command, so "not reproduced" by construction; agreed with the root cause and that the inputs are legal, and noted the static evidence "otherwise strongly favors a genuine defect". Separate re-runs of the finder's test failed 7 of 7. |
| Devil's advocate | real | Built the intended-behaviour case: weak for Infinity, moderate for the tiny-argument zero. Own doc-values sort test fails on main and 10.5.2. |
| Adjudication | confirmed (high) | Own 7-test JUnit: 6 of 7 fail with assertions on and off; re-ran the independent reproduction's test: 10 of 12. Rated the acosh NaN rule and the Infinity collapse the strongest parts. |
| Follow-up and credibility audit | reproduces; likelihood contrived | History runs (4.5.1 absent, 4.6.0 to main fail), a ranking and filter probe, the prior-art search (new) and re-runs; the audit's fresh-seed re-run and direct probe confirmed the thresholds and lowered the likelihood from rare to contrived. |
| This doc | reproduced, fix validated | Gradle on 645d4fe14a20; the 10.6.0 and 9.12.3 jars; the value probe on main, 10.6.0 and the fix; the recommended fix passes the repro, TestMathUtil, the expressions module and the core lint checks. |
The diagram as a still image, generated with AI and checked against the code. It is under 10 MB, the limit for a GitHub attachment.
- PNG 519 KB
Finding record, Lucene and upstream sources, repro files and diagram notes1,267 words
- Registry title
- MathUtil.asinh, acosh and atanh use the textbook log formulas without range reduction: asinh and acosh return Infinity for every finite |x| >= 2^512, asinh and atanh return 0 for tiny nonzero x, and acosh of a large negative x returns an infinity instead of the documented NaN, also through the expression functions asinh, acosh and atanh
- Area
- util / MathUtil inverse hyperbolic functions (asinh, acosh, atanh), reached through the expressions module's default functions asinh, acosh and atanh
- Priority
- Low, rank 867: MathUtil and the expression functions asinh, acosh and atanh collapse only for extreme magnitudes or near-zero values, a boundary-value error.
- Impact class
- silent wrong results
- Impact note
- Direct callers of MathUtil.asinh/acosh/atanh and users of the optional expressions module whose expressions call asinh, acosh or atanh (default function set, over literals or bound values such as doc values) get Infinity, 0 or an infinity of the wrong kind with no error when the argument is a finite |x| >= 2^512 (about 1.34e154; asinh, acosh), 0 < |x| <= 2^-53 (asinh) or 2^-54 (atanh), or x <= about -6.7e7 (acosh, documented NaN), which skews expression scores, sort keys (distinct documents tie at Infinity) and threshold filters at query time; no default code path calls these functions, the util package is described as 'primarily internal' and JavascriptCompiler is @lucene.experimental, every release since 4.6.0 is affected, and the adjudication and all verifiers rate the realistic exposure low.
- Summary
- You call MathUtil.asinh(1e200), or evaluate the expression asinh(x) for a document whose x is 1e200, and expect about 461.21, because the inverse hyperbolic sine of a finite number is finite (at most about 710.5). Instead you get Infinity, because the code computes log(sqrt(x*x + 1) + x) and x*x overflows for every |x| >= 2^512; acosh does the same, asinh(1e-17) and atanh(1e-17) return 0 instead of 1e-17 because 1 + x*x (or (1+x)/(1-x)) rounds to exactly 1, and acosh(-1e8) returns -Infinity (acosh(-1e200) +Infinity) although its javadoc says that any argument below 1 gives NaN.
- Introduced
- c73f175c71ee02d057819037105b4e55d4099567 ('LUCENE-5207: initial patch from ryan', committed by rmuir, patch by rjernst, on the SVN feature branch lucene5207, r1522767), landed on trunk as eecd78f37a128dac326294ebf550eae0bbb04434 (r1523462, lineage of 6.0.0 and later) and on branch_4x as 08df432f8ba20370ed18b8933603ceba4007437b (r1523470, lineage of 4.6.0-5.5.5), MathUtil.java identical in all three; JIRA LUCENE-5207 'lucene expressions module' (migrated as apache/lucene#6271), SVN era, no pull request. It added asinh/acosh/atanh together with the expressions module and bound them as expression functions. [read: git blame of the formula lines and git log -S on each of the three formula strings return only c73f175c71ee; the only later commit in these lines is 2d6ad2fee6d (LUCENE-9570, javadoc reformatting); exec: 4.5.1 lacks the API (compile error), 4.6.0 fails]; not a regression: the values have been wrong since the API was added (2013-09-13 (c73f175c71ee; trunk merge and branch_4x backport 2013-09-15))
- First affected version
- 4.6.0 [exec] (released 2013-11-22; 4.5.1 has no MathUtil.asinh/acosh/atanh and no expressions module, compile error [exec])
- Affected versions
- every release from 4.6.0 through 10.5.2 (the latest 9.x 9.12.3 and the latest 10.x before 10.6.0, 10.5.2) and main ea57360912ed; not fixed upstream (upstream main e1c4184cb983, branch_10x, branch_10_6 and branch_9_12 still have the unguarded formulas, read 2026-10-26). [exec] the version-neutral main() MathUtilHyperbolicMain fails with identical values on 4.6.0 (-ea and -da), 4.10.4, 5.5.5, 6.6.6, 7.7.3, 8.11.4, 9.12.3, 10.5.2 and main (-ea and -da); the expressions path fails on 4.6.0 (4.x Expression API) and main; the acosh negative-argument probe fails on 4.6.0, 9.12.3, 10.5.2 and main; the independent reproduction's JUnit fails 10 of 12 on main, 10.5.2 and 9.12.3 (-ea and -da). [read] blame and pickaxe show no change to the formula lines since c73f175c71ee, so the releases between the executed ones are covered by reading only; 10.6.0 (released 2026-10-29) fails too [exec: the minimal test and the value probe on the 10.6.0 jars, 2026-10-31]. All runs on JDK 25.0.3, aarch64 Linux, not each release's own JDK.
- Status
- documented (brief, checks and a per-finding doc exist)
- Verification
- Confirmed by AI-run checks: an independent repro, a cross-model review that could only read the code (its low-confidence “not real” is procedural), an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
- Found by
- semi-formal: code reading; also reached by Z3/cvc5
- Upstream and prior reports
- none found (2026-10-26, follow-up prior-report record verdict new, 88 logged queries and reads): a scripted prior-report search over apache/lucene issues and PRs (the bare terms asinh, acosh and atanh return 0 items; hyperbolic, MathUtil, MathUtil.java and symptom phrases give no relevant hit), commits, downstream repositories, the dev@, java-user@, issues@ and commits@ lists and the JIRA archive; further GitHub API searches (log1p, fdlibm, StrictMath, JavascriptCompiler titles, expressions with Infinity, NaN or precision); a changed-file scan of all 411 open apache/lucene PRs (none touches MathUtil.java or JavascriptCompiler.properties); LUCENE, SOLR and LUCENENET JIRA searches; the Lucene.NET and Solr lists; 12 web queries; and a full read of the LUCENE-5207 discussion (#6271, 89 JIRA comments). Closest items, none a report: #6271 / LUCENE-5207 (introduced the code; nothing on range or accuracy), #5234 / LUCENE-4162 (the MathUtil.log argument check, another method), apache/lucenenet#297 (speed of the ported functions only; accuracy never came up), and Elasticsearch's own range-reduced asinh/acosh/atanh of 2026 (elastic/elasticsearch#141128, #142184, #142213; they never mention Lucene's MathUtil). Not fixed upstream (main e1c4184cb983, branch_10x, branch_10_6, branch_9_12). Gaps: GitHub's search rate limit cut a few restrictive queries (for example 'MathUtil.asinh', subsumed by the bare-term searches); private reports to security@lucene.apache.org are not visible.
- Lucene commit tested
- main
645d4fe14a20(2026-10-28): the minimal test fails through gradle in a scratch worktree (assertions on and off) and through a standalone runner, which also fails it on 10.6.0 and 9.12.3; a value probe gives the same wrong values on main and 10.6.0. With the recommended fix the test passes (assertions on and off), and so doTestMathUtil, the whole expressions module and the core format, ECJ and forbidden-apis checks. Earlier runs: 4.5.1 (API absent) and 4.6.0 to 10.5.2 and mainea57360912ed(version-neutral program, identical values). The hunt, the verifiers and the adjudication ran mainea57360912ed, a refresh re-ran the repros ona9779ef3c193; the files involved are identical in all three.
- Finding record
lucene/core/src/java/org/apache/lucene/util/MathUtil.java:90-154 (javadoc special cases; formulas at :112, :128, :153)lucene/core/src/test/org/apache/lucene/util/TestMathUtil.java:105-127 (acosh NaN asserted for 0.9, 0, -0, -0.9, -1, -10 and -Infinity; epsilon 1e-6; largest argument 1234567.89)lucene/expressions/src/resources/org/apache/lucene/expressions/js/JavascriptCompiler.properties:23,25,28 (acosh, asinh, atanh bound to org.apache.lucene.util.MathUtil)lucene/expressions/src/java/org/apache/lucene/expressions/js/package-info.java:31-32 (asinh, acosh, atanh listed as expression functions); JavascriptCompiler.java:111 (@lucene.experimental), :155 (compile(String) uses DEFAULT_FUNCTIONS)lucene/core/src/java/org/apache/lucene/util/package-info.java:35 ('APIs in this package are primarily internal and may change between releases')
- The finding in 30 seconds
lucene/core/src/java/org/apache/lucene/util/MathUtil.java:90-154 (javadoc :91, :116, :124, :132; formulas :112, :128, :153) at 645d4fe14a20lucene/expressions/src/resources/org/apache/lucene/expressions/js/JavascriptCompiler.properties:23, :25, :28, :31, :43, :46; lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java:154-155, :866; lucene/expressions/src/java/org/apache/lucene/expressions/js/package-info.java:31-32JDK 25 `javap java.lang.Math`: sinh, cosh, tanh, log1p, no asinh/acosh/atanh (run for this doc)
- Who is affected
lucene/core/src/java/org/apache/lucene/util/MathUtil.java:22 (public final class, no @lucene.internal)grep over lucene/ at 645d4fe14a20: asinh/acosh/atanh only in MathUtil.java, expressions js/package-info.java and JavascriptCompiler.properties outside tests (run for this doc)
- Why it looks unintended
lucene/core/src/java/org/apache/lucene/util/MathUtil.java:90-143 (javadoc); lucene/core/src/test/org/apache/lucene/util/TestMathUtil.java:105-126, :146-153, :177-182; lucene/expressions/src/test/org/apache/lucene/expressions/js/TestJavascriptFunction.java:23, :54-57, :74-81, :108-113https://github.com/apache/lucene/issues/5234 (LUCENE-4162 body, Uwe Schindler; read 2026-10-31)
- History
a Lucene clone release tags releases/lucene/9.12.3 and releases/lucene/10.6.0 (MathUtil.java:112, :128, :153 unchanged)
- TestMathUtilAsinhLargeArgument.java (one test class):
repro/LFV-327/src/org/apache/lucene/util/TestMathUtilAsinhLargeArgument.java(27 lines)
Made in parallel with this doc from the minimal repro and the executed values.
assets/LFV-327/bug.png(519 KB)
