From aa8f16328cf14b977dca36e730d4552873b87969 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:39:46 +0000 Subject: [PATCH 1/3] feat(math): add missing math functions (log base, modf, frexp, ldexp, remainder, isclose, nextafter, ulp, exp2, cbrt) Add the following Python 3.8-3.12 math functions that were absent from the existing Math.fs bindings: - log(x, base): logarithm with explicit base - ldexp(x, i): x * 2**i - frexp(x): decompose into mantissa and exponent - modf(x): fractional and integer parts - remainder(x, y): IEEE 754 remainder (Python 3.8+) - isclose(a, b) / isclose(a, b, rel_tol, abs_tol): approximate equality - nextafter(x, y): next representable float (Python 3.9+) - ulp(x): value of least significant bit (Python 3.9+) - exp2(x): 2**x (Python 3.11+) - cbrt(x): cube root (Python 3.11+) Also adds tests for each new binding in TestMath.fs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 11 +-------- src/stdlib/Math.fs | 34 +++++++++++++++++++++++++++ test/TestMath.fs | 57 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 749c832..8b1dd97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,18 +9,9 @@ All notable changes to this project will be documented in this file. ## Unreleased -### 🐞 Bug Fixes - -* Fix `math.factorial` binding: changed signature from `float -> float` to `int -> int` to match Python 3.12+ where float arguments raise `TypeError`. Fixes test to use integer literals. -* Fix `math.copysign` binding: `y` parameter was incorrectly typed as `int`, now correctly `float` -* Fix `math.fmod` binding: parameters were incorrectly typed as `int -> int -> int`, now correctly `float * float -> float` -* Fix `math.comb`, `math.pow`, `math.atan2`, `math.dist` bindings: converted curried parameter syntax to tupled for consistency - ### ✨ Enhancements -* Add missing `math` module constants: `pi`, `e`, `tau`, `inf`, `nan` -* Add missing `math` module functions: `sqrt`, `degrees`, `radians`, `trunc`, `hypot`, `fsum`, `isqrt`, `prod`, `perm`, `acosh`, `asinh`, `atanh`, `cosh`, `sinh`, `tanh`, `erf`, `erfc`, `gamma`, `lgamma` -* Fix `math.dist` signature to accept float arrays (for multi-dimensional distance) +* Add missing `math` module functions: `log` with base, `ldexp`, `frexp`, `modf`, `remainder`, `isclose`, `nextafter`, `ulp`, `exp2`, `cbrt` ## 5.0.0-rc.3 - 2026-04-16 diff --git a/src/stdlib/Math.fs b/src/stdlib/Math.fs index 897d8cd..198c59c 100644 --- a/src/stdlib/Math.fs +++ b/src/stdlib/Math.fs @@ -69,6 +69,25 @@ type IExports = /// Return the integer square root of the non-negative integer n /// See https://docs.python.org/3/library/math.html#math.isqrt abstract isqrt: n: int -> int + /// Return x * (2**i) accurately + /// See https://docs.python.org/3/library/math.html#math.ldexp + abstract ldexp: x: float * i: int -> float + /// Return the mantissa and exponent of x as the pair (m, e) + /// See https://docs.python.org/3/library/math.html#math.frexp + abstract frexp: x: float -> float * int + /// Return the fractional and integer parts of x + /// See https://docs.python.org/3/library/math.html#math.modf + abstract modf: x: float -> float * float + /// Return IEEE 754-style remainder of x with respect to y (Python 3.8+) + /// See https://docs.python.org/3/library/math.html#math.remainder + abstract remainder: x: float * y: float -> float + /// Return True if the values a and b are close to each other + /// See https://docs.python.org/3/library/math.html#math.isclose + abstract isclose: a: float * b: float -> bool + /// Return True if the values a and b are close to each other with custom tolerances + /// See https://docs.python.org/3/library/math.html#math.isclose + [] + abstract isclose: a: float * b: float * ?rel_tol: float * ?abs_tol: float -> bool /// Return the least common multiple of the integers /// See https://docs.python.org/3/library/math.html#math.lcm abstract lcm: [] ints: int[] -> int @@ -107,6 +126,12 @@ type IExports = /// Check if x is a NaN (not a number) /// See https://docs.python.org/3/library/math.html#math.isnan abstract isnan: x: int -> bool + /// Return the next floating-point value after x towards y (Python 3.9+) + /// See https://docs.python.org/3/library/math.html#math.nextafter + abstract nextafter: x: float * y: float -> float + /// Return the value of the least significant bit of the float x (Python 3.9+) + /// See https://docs.python.org/3/library/math.html#math.ulp + abstract ulp: x: float -> float // ======================================================================== // Power and logarithmic functions @@ -121,6 +146,9 @@ type IExports = /// Return the natural logarithm of x /// See https://docs.python.org/3/library/math.html#math.log abstract log: x: float -> float + /// Return the logarithm of x to the given base + /// See https://docs.python.org/3/library/math.html#math.log + abstract log: x: float * ``base``: float -> float /// Return the natural logarithm of 1+x (base e) /// See https://docs.python.org/3/library/math.html#math.log1p abstract log1p: x: float -> float @@ -136,6 +164,12 @@ type IExports = /// Return the square root of x /// See https://docs.python.org/3/library/math.html#math.sqrt abstract sqrt: x: float -> float + /// Return 2 raised to the power x (Python 3.11+) + /// See https://docs.python.org/3/library/math.html#math.exp2 + abstract exp2: x: float -> float + /// Return the cube root of x (Python 3.11+) + /// See https://docs.python.org/3/library/math.html#math.cbrt + abstract cbrt: x: float -> float // ======================================================================== // Trigonometric functions diff --git a/test/TestMath.fs b/test/TestMath.fs index ea0fb96..89c2ab1 100644 --- a/test/TestMath.fs +++ b/test/TestMath.fs @@ -224,3 +224,60 @@ let ``test gamma works`` () = [] let ``test lgamma works`` () = math.lgamma 1.0 |> equal 0.0 + +[] +let ``test log with base works`` () = + math.log (8.0, 2.0) |> equal 3.0 + math.log (100.0, 10.0) |> equal 2.0 + +[] +let ``test ldexp works`` () = + math.ldexp (1.0, 3) |> equal 8.0 + math.ldexp (0.5, 2) |> equal 2.0 + +[] +let ``test frexp works`` () = + let m, e = math.frexp 8.0 + m |> equal 0.5 + e |> equal 4 + +[] +let ``test modf works`` () = + let frac, intPart = math.modf 3.7 + (frac > 0.699 && frac < 0.701) |> equal true + intPart |> equal 3.0 + +[] +let ``test remainder works`` () = + math.remainder (10.0, 3.0) |> equal 1.0 + math.remainder (5.0, 2.0) |> equal 1.0 + +[] +let ``test isclose works`` () = + math.isclose (1.0, 1.0) |> equal true + math.isclose (1.0, 2.0) |> equal false + +[] +let ``test isclose with tolerances works`` () = + math.isclose (1.0, 1.001, rel_tol=0.01) |> equal true + math.isclose (1.0, 2.0, abs_tol=0.1) |> equal false + +[] +let ``test nextafter works`` () = + let x = math.nextafter (1.0, 2.0) + (x > 1.0) |> equal true + +[] +let ``test ulp works`` () = + let x = math.ulp 1.0 + (x > 0.0) |> equal true + +[] +let ``test exp2 works`` () = + math.exp2 3.0 |> equal 8.0 + math.exp2 0.0 |> equal 1.0 + +[] +let ``test cbrt works`` () = + math.cbrt 27.0 |> equal 3.0 + math.cbrt 8.0 |> equal 2.0 From 67e692d1e0b54ed22bef188f179965c152204f32 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 16 Apr 2026 17:45:08 +0200 Subject: [PATCH 2/3] fix: revert CHANGELOG.md to main, stop overwriting previous entries Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b1dd97..749c832 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,18 @@ All notable changes to this project will be documented in this file. ## Unreleased +### 🐞 Bug Fixes + +* Fix `math.factorial` binding: changed signature from `float -> float` to `int -> int` to match Python 3.12+ where float arguments raise `TypeError`. Fixes test to use integer literals. +* Fix `math.copysign` binding: `y` parameter was incorrectly typed as `int`, now correctly `float` +* Fix `math.fmod` binding: parameters were incorrectly typed as `int -> int -> int`, now correctly `float * float -> float` +* Fix `math.comb`, `math.pow`, `math.atan2`, `math.dist` bindings: converted curried parameter syntax to tupled for consistency + ### ✨ Enhancements -* Add missing `math` module functions: `log` with base, `ldexp`, `frexp`, `modf`, `remainder`, `isclose`, `nextafter`, `ulp`, `exp2`, `cbrt` +* Add missing `math` module constants: `pi`, `e`, `tau`, `inf`, `nan` +* Add missing `math` module functions: `sqrt`, `degrees`, `radians`, `trunc`, `hypot`, `fsum`, `isqrt`, `prod`, `perm`, `acosh`, `asinh`, `atanh`, `cosh`, `sinh`, `tanh`, `erf`, `erfc`, `gamma`, `lgamma` +* Fix `math.dist` signature to accept float arrays (for multi-dimensional distance) ## 5.0.0-rc.3 - 2026-04-16 From fa0f73ff1e43d63696b569e84dd03eb4b11273f1 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 16 Apr 2026 17:50:31 +0200 Subject: [PATCH 3/3] fix: ldexp needs nativeint (Python int), cbrt test uses tolerance - ldexp's second arg must be a native Python int, not Fable's Int32 - cbrt(27.0) returns 3.0000000000000004, use isclose for comparison Co-Authored-By: Claude Opus 4.6 (1M context) --- src/stdlib/Math.fs | 2 +- test/TestMath.fs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/stdlib/Math.fs b/src/stdlib/Math.fs index 198c59c..07a72e0 100644 --- a/src/stdlib/Math.fs +++ b/src/stdlib/Math.fs @@ -71,7 +71,7 @@ type IExports = abstract isqrt: n: int -> int /// Return x * (2**i) accurately /// See https://docs.python.org/3/library/math.html#math.ldexp - abstract ldexp: x: float * i: int -> float + abstract ldexp: x: float * i: nativeint -> float /// Return the mantissa and exponent of x as the pair (m, e) /// See https://docs.python.org/3/library/math.html#math.frexp abstract frexp: x: float -> float * int diff --git a/test/TestMath.fs b/test/TestMath.fs index 89c2ab1..6141b45 100644 --- a/test/TestMath.fs +++ b/test/TestMath.fs @@ -232,8 +232,8 @@ let ``test log with base works`` () = [] let ``test ldexp works`` () = - math.ldexp (1.0, 3) |> equal 8.0 - math.ldexp (0.5, 2) |> equal 2.0 + math.ldexp (1.0, 3n) |> equal 8.0 + math.ldexp (0.5, 2n) |> equal 2.0 [] let ``test frexp works`` () = @@ -279,5 +279,5 @@ let ``test exp2 works`` () = [] let ``test cbrt works`` () = - math.cbrt 27.0 |> equal 3.0 - math.cbrt 8.0 |> equal 2.0 + math.isclose (math.cbrt 27.0, 3.0) |> equal true + math.isclose (math.cbrt 8.0, 2.0) |> equal true