diff --git a/src/fpy/semantics.py b/src/fpy/semantics.py index c8f19a6..a11b7db 100644 --- a/src/fpy/semantics.py +++ b/src/fpy/semantics.py @@ -2706,6 +2706,12 @@ def visit_AstBinaryOp(self, node: AstBinaryOp, state: CompileState): folded_value = lhs_value / rhs_value elif node.op == BinaryStackOp.EXPONENT: folded_value = lhs_value**rhs_value + if isinstance(folded_value, complex): + # float ** float returns a complex number for a negative + # base and a fractional exponent, where the Decimal path + # raises decimal.InvalidOperation + state.err("Domain error", node) + return elif node.op == BinaryStackOp.FLOOR_DIVIDE: # Floor toward -inf (Python `//`), matching the runtime backends. if isinstance(lhs_value, int) and isinstance(rhs_value, int): diff --git a/test/fpy/test_arithmetic.py b/test/fpy/test_arithmetic.py index d3c176b..9efb469 100644 --- a/test/fpy/test_arithmetic.py +++ b/test/fpy/test_arithmetic.py @@ -56,6 +56,24 @@ def test_const_complex_pow(self, fprime_test_api): assert_compile_failure(fprime_test_api, seq) + def test_const_complex_pow_float_operands(self, fprime_test_api): + """A negative base with a fractional exponent is a domain error even + when an operand has already been folded to a Python float, where + ``float ** float`` returns a complex number instead of raising.""" + seq = """ +x: F64 = F64(-8.0) ** 0.5 +""" + + assert_compile_failure(fprime_test_api, seq, match="Domain error") + + def test_const_complex_pow_float_exponent(self, fprime_test_api): + """Same when it is the exponent that carries the float type.""" + seq = """ +y: F64 = (-2.0) ** F64(0.5) +""" + + assert_compile_failure(fprime_test_api, seq, match="Domain error") + def test_very_large_const_pow(self, fprime_test_api): seq = """ 10.0 ** 1000