karatelabs/karate · error · JsErrorException (rangeError)

Exponent must be non-negative

Error message

Exponent must be non-negative

What it means

BigInt exponentiation (`**`) with a negative exponent is undefined per the JS spec (a negative BigInt power is not an integer), so Terms.java throws a RangeError instead of calling BigInteger.pow. This matches V8's 'Exponent must be non-negative'.

Solutions

  1. Ensure the exponent is non-negative before the operation
  2. Use Number arithmetic for negative exponents: `2 ** -3` gives 0.25
  3. Compute the positive power and divide: 1n / (2n ** 3n) if an exact BigInt reciprocal fraction is acceptable

Example fix

// before
var p = baseN ** expN;
// after
var p = expN < 0n ? (1n / (baseN ** -expN)) : (baseN ** expN);
Defensive patterns

Strategy: validation

Validate before calling

if (exp < 0n) throw new Error('BigInt exponent must be non-negative: ' + exp);

Type guard

function canBigIntPow(b, e) { return typeof b === 'bigint' && typeof e === 'bigint' && e >= 0n; }

Try / catch

try { p = base ** exp; } catch (e) { if (String(e).includes('Exponent must be non-negative')) p = Number(base) ** Number(exp); else throw e; }

Prevention

When it happens

Trigger: Evaluating `2n ** -3n` or `base ** exp` where both are BigInt and exp < 0n in karate-js.

Common situations: Generic math helper functions written for numbers reused with BigInt operands; computing reciprocal/powers like 10n ** -n when porting numeric code.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12). Data as JSON: /api/errors/d04d2b4f4add411f. Report an issue: GitHub.

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/js/Terms.java:809

                throw JsErrorException.rangeError("Division by zero");
            }
            // Java BigInteger.remainder matches JS BigInt % semantics (sign follows dividend)
            return narrowBigInt(((BigInteger) lhs).remainder(r));
        }
        double result = lhs.doubleValue() % rhs.doubleValue();
        return narrow(result);
    }

    static Object exp(Object lhsObject, Object rhsObject, CoreContext context) {
        Number lhs = toNumericOperand(lhsObject, context);
        if (context != null && context.isError()) return UNDEFINED;
        Number rhs = toNumericOperand(rhsObject, context);
        if (context != null && context.isError()) return UNDEFINED;
        if (isBigIntOp(lhs, rhs)) {
            requireBothBigInt(lhs, rhs, "**");
            BigInteger r = (BigInteger) rhs;
            if (r.signum() < 0) {
                throw JsErrorException.rangeError("Exponent must be non-negative");
            }
            return narrowBigInt(((BigInteger) lhs).pow(r.intValueExact()));
        }
        double result = Math.pow(lhs.doubleValue(), rhs.doubleValue());
        return narrow(result);
    }

    static Object add(Object lhs, Object rhs, CoreContext context) {
        // Spec evaluation of binary +: ToPrimitive both operands first (default hint),
        // then string-or-number dispatch on the *primitives*. ObjectLike on either side
        // is the rare case — primitives short-circuit through the existing fast path.
        if (lhs instanceof ObjectLike) {
            lhs = toPrimitive(lhs, "default", context);
            if (context != null && context.isError()) return UNDEFINED;
        }
        if (rhs instanceof ObjectLike) {
            rhs = toPrimitive(rhs, "default", context);
            if (context != null && context.isError()) return UNDEFINED;

View on GitHub (pinned to a22eb90246)