mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-14 22:29:17 +00:00
fix(ecmascript): fold ** with Number::exponentiate, not IEEE pow (#25644)
`f64::powf` implements IEEE 754 `pow`, which returns `1` for a base of `1` whatever the exponent. [`Number::exponentiate`](https://tc39.es/ecma262/#sec-numeric-types-number-exponentiate) instead returns `NaN` when the exponent is `NaN` (step 1), and when the base has magnitude `1` and the exponent is infinite (steps 9-10). Constant folding called `powf` directly, so: ```js 1 ** Infinity // spec: NaN oxc folded to: 1 (-1) ** Infinity // spec: NaN oxc folded to: 1 1 ** -Infinity // spec: NaN oxc folded to: 1 1 ** NaN // spec: NaN oxc folded to: 1 ``` `1` is truthy and `NaN` is not, so an `if` guarded by such an expression takes the wrong branch. The one case the two agree on — an exponent of `0` returning `1` whatever the base, including `NaN ** 0` and `Infinity ** 0` — is unchanged, since `powf` already gets it right and the spec checks the exponent before the base. `cargo minsize` shows no snapshot change. Found by the minifier fuzzer (#25594). --- Written with AI assistance (Claude Code). Reviewed by me, and every expected value in the added test was checked against Node.js. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b846abc2a4
commit
f6000ac141
2 files changed
+38
-1
No files matched your search
@@ -263,7 +263,22 @@ fn binary_operation_evaluate_value_to<'a>(
|
||||
BinaryOperator::Exponential => {
|
||||
let lval = left.evaluate_value_to_number(ctx)?;
|
||||
let rval = right.evaluate_value_to_number(ctx)?;
|
||||
let result = lval.powf(rval);
|
||||
// `Number::exponentiate` is not IEEE 754 `pow`, which `f64::powf`
|
||||
// implements. `pow` returns `1` for a base of `1` whatever the
|
||||
// exponent, and for an exponent of `0` whatever the base; the
|
||||
// abstract operation instead returns `NaN` when the exponent is
|
||||
// `NaN`, and when the base has magnitude `1` and the exponent is
|
||||
// infinite. Only the exponent-is-zero case is shared, and `powf`
|
||||
// already agrees there.
|
||||
// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation>
|
||||
// <https://tc39.es/ecma262/#sec-numeric-types-number-exponentiate>
|
||||
#[expect(clippy::float_cmp)]
|
||||
let base_is_one = lval.abs() == 1.0;
|
||||
let result = if rval.is_nan() || (base_is_one && rval.is_infinite()) {
|
||||
f64::NAN
|
||||
} else {
|
||||
lval.powf(rval)
|
||||
};
|
||||
// For now, ignore the result if it large or has a decimal part
|
||||
// so that the output does not become bigger than the input.
|
||||
if result.is_finite() && (result.fract() != 0.0 || result.log10() > 4.0) {
|
||||
|
||||
@@ -1139,6 +1139,28 @@ fn test_fold_exponential() {
|
||||
fold("x = null ** 0", "x = 1");
|
||||
}
|
||||
|
||||
/// `Number::exponentiate` is not IEEE 754 `pow`. It returns `NaN` whenever the
|
||||
/// exponent is `NaN`, and whenever the base has magnitude `1` and the exponent
|
||||
/// is infinite — both cases where `pow` is specified to return `1`.
|
||||
///
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation>
|
||||
#[test]
|
||||
fn test_fold_exponential_disagrees_with_ieee_pow() {
|
||||
fold("x = 1 ** Infinity", "x = NaN");
|
||||
fold("x = 1 ** -Infinity", "x = NaN");
|
||||
fold("x = (-1) ** Infinity", "x = NaN");
|
||||
fold("x = (-1) ** -Infinity", "x = NaN");
|
||||
fold("x = true ** Infinity", "x = NaN");
|
||||
fold("x = 1 ** NaN", "x = NaN");
|
||||
fold("x = (-1) ** NaN", "x = NaN");
|
||||
|
||||
// The cases `pow` and `Number::exponentiate` agree on.
|
||||
fold("x = 2 ** Infinity", "x = Infinity");
|
||||
fold("x = 2 ** NaN", "x = NaN");
|
||||
fold("x = NaN ** 0", "x = 1");
|
||||
fold("x = Infinity ** 0", "x = 1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fold_arithmetic_undefined_null_operands() {
|
||||
// `undefined` has no literal form (it prints as `void 0`), so it never
|
||||
|
||||
Reference in new issue
Block a user