diff --git a/packages/melonjs/CHANGELOG.md b/packages/melonjs/CHANGELOG.md index fa37f15a7..69c3d7f13 100644 --- a/packages/melonjs/CHANGELOG.md +++ b/packages/melonjs/CHANGELOG.md @@ -23,6 +23,7 @@ - UI: `UITextButton#bitmapText`, for `label` ### Fixed +- Math: `isPowerOfTwo()` rejects zero, negative and fractional values instead of accepting them after 32-bit coercion, and handles powers and non-powers beyond the 32-bit range (thanks @mmporong) - Input: a child cropped out of view by a container with `clipping`, or scrolled out of a `UIPanel`, no longer gets pointer events where it is not drawn - UI: a `UITextButton` moved after it was built draws its background where it is, not where it was first put - UI: a blocking `UIPanel` stays opaque to hover for as long as the pointer is on it, not only on the frame the pointer enters diff --git a/packages/melonjs/src/math/math.ts b/packages/melonjs/src/math/math.ts index 26dbdecde..fa0ff085f 100644 --- a/packages/melonjs/src/math/math.ts +++ b/packages/melonjs/src/math/math.ts @@ -34,7 +34,11 @@ export const EPSILON = 0.000001; * @returns true if number is a power of two */ export function isPowerOfTwo(val: number) { - return (val & (val - 1)) === 0; + if (!Number.isInteger(val) || val <= 0) { + return false; + } + const exponent = Math.log2(val); + return Number.isInteger(exponent) && val === 2 ** exponent; } /** diff --git a/packages/melonjs/tests/math.spec.js b/packages/melonjs/tests/math.spec.js index e54cef701..3eb362aba 100644 --- a/packages/melonjs/tests/math.spec.js +++ b/packages/melonjs/tests/math.spec.js @@ -328,6 +328,27 @@ describe("Math", () => { it("32 is a Power of 2", () => { expect(math.isPowerOfTwo(32)).toEqual(true); }); + it("0 and negative values are not Powers of 2", () => { + expect(math.isPowerOfTwo(0)).toEqual(false); + expect(math.isPowerOfTwo(-2)).toEqual(false); + }); + it("fractional values are not Powers of 2", () => { + expect(math.isPowerOfTwo(2.5)).toEqual(false); + }); + it("supports Powers of 2 outside the 32-bit range", () => { + expect(math.isPowerOfTwo(2 ** 40)).toEqual(true); + expect(math.isPowerOfTwo(3 * 2 ** 32)).toEqual(false); + }); + it("rejects large non-Powers whose logarithms round to integers", () => { + for (const value of [ + Number.MAX_SAFE_INTEGER, + 2 ** 53 + 2, + 2 ** 53 + 4, + 2 ** 54 - 2, + ]) { + expect(math.isPowerOfTwo(value)).toEqual(false); + } + }); it("1027 is not a Power of 2", () => { expect(math.isPowerOfTwo(1027)).toEqual(false); });