Skip to content

Commit dcc9624

Browse files
dbrattliclaude
andcommitted
[Python] Add Math.DivRem support for int, int64, and bigint
Implement div_rem in int32.py, long.py, and big_int.py, supporting both the 2-arg (returns tuple) and 3-arg (outref) overloads. Route Math.DivRem and bigint.DivRem through these library functions in Python Replacements. For bigint, uses truncated division (adjusting Python's floored //) to match .NET semantics. int32/int64 Rust wrappers already truncate. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d8572aa commit dcc9624

7 files changed

Lines changed: 107 additions & 35 deletions

File tree

src/Fable.Cli/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111

12+
* [Python] Add `Math.DivRem` support for int, int64, and bigint (by @dbrattli)
1213
* [Python] Fix modulo with negative numbers using Python floored semantics instead of .NET truncated semantics for bigint (fixes #4462) (by @dbrattli)
1314
* [Beam] Fix `System.String.Concat` with 4+ arguments not being supported (by @dbrattli)
1415
* [TS/Python] Fix invalid `this` argument type in structs (#4453) (by @ncave)

src/Fable.Compiler/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111

12+
* [Python] Add `Math.DivRem` support for int, int64, and bigint (by @dbrattli)
1213
* [Python] Fix modulo with negative numbers using Python floored semantics instead of .NET truncated semantics for bigint (fixes #4462) (by @dbrattli)
1314
* [Beam] Fix `System.String.Concat` with 4+ arguments not being supported (by @dbrattli)
1415
* [TS/Python] Fix invalid `this` argument type in structs (#4453) (by @ncave)

src/Fable.Transforms/Python/Replacements.fs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1267,6 +1267,16 @@ let operators (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr o
12671267
| _ ->
12681268
Helper.ImportedCall("math", "trunc", t, args, i.SignatureArgTypes, ?loc = r)
12691269
|> Some
1270+
| "DivRem", _ ->
1271+
let modName =
1272+
match i.SignatureArgTypes with
1273+
| Number(BigInt, _) :: _ -> "big_int"
1274+
| Number(Int64, _) :: _
1275+
| Number(UInt64, _) :: _ -> "long"
1276+
| _ -> "int32"
1277+
1278+
Helper.LibCall(com, modName, "div_rem", t, args, i.SignatureArgTypes, ?loc = r)
1279+
|> Some
12701280
| "Sign", _ ->
12711281
match args with
12721282
| ExprType(Number(Decimal, _)) :: _ ->
@@ -2412,7 +2422,7 @@ let bigints (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg:
24122422
Helper.LibCall(com, "BigInt", methodName, t, args, i.SignatureArgTypes, ?loc = r)
24132423
|> Some
24142424
| None, "DivRem" ->
2415-
Helper.LibCall(com, "big_int", "divRem", t, args, i.SignatureArgTypes, ?loc = r)
2425+
Helper.LibCall(com, "big_int", "div_rem", t, args, i.SignatureArgTypes, ?loc = r)
24162426
|> Some
24172427
| None, meth when meth.StartsWith("get_", StringComparison.Ordinal) ->
24182428
Helper.LibValue(com, "big_int", meth, t) |> Some

src/fable-library-py/fable_library/big_int.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from decimal import Decimal
2-
from typing import Any, SupportsInt
2+
from typing import Any, SupportsInt, overload
33

44
from .core import FSharpRef
55

@@ -89,6 +89,25 @@ def op_modulus(a: int, b: int) -> int:
8989
return r
9090

9191

92+
@overload
93+
def div_rem(x: int, y: int) -> tuple[int, int]: ...
94+
95+
96+
@overload
97+
def div_rem(x: int, y: int, out: FSharpRef[int]) -> int: ...
98+
99+
100+
def div_rem(x: int, y: int, out: FSharpRef[int] | None = None) -> int | tuple[int, int]:
101+
# .NET uses truncated division; Python // floors toward -inf.
102+
# Adjust when signs differ to get truncation toward zero.
103+
q = -((-x) // y) if (x < 0) != (y < 0) else x // y
104+
r = x - q * y
105+
if out is None:
106+
return (q, r)
107+
out.contents = r
108+
return q
109+
110+
92111
def op_right_shift(a: int, num_bits: int) -> int:
93112
return a >> num_bits
94113

@@ -272,6 +291,7 @@ def try_parse(string: str, def_value: FSharpRef[int]) -> bool:
272291
"abs",
273292
"add",
274293
"compare",
294+
"div_rem",
275295
"divide",
276296
"equals",
277297
"from_int32",

src/fable-library-py/fable_library/int32.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from .core import int8, int16, int32
1+
from typing import SupportsInt, overload
2+
3+
from .core import FSharpRef, int8, int16, int32
24
from .core._core import get_range
35
from .core._core import parse_int32 as parse
46
from .core._core import try_parse_int32 as try_parse
@@ -12,6 +14,24 @@ def sign(x: int32) -> int32:
1214
return int32.NEG_ONE if x < int32.ZERO else int32.ONE if x > int32.ZERO else int32.ZERO
1315

1416

17+
@overload
18+
def div_rem[T: SupportsInt](x: T, y: T) -> tuple[T, T]: ...
19+
20+
21+
@overload
22+
def div_rem[T: SupportsInt](x: T, y: T, out: FSharpRef[T]) -> T: ...
23+
24+
25+
def div_rem[T: SupportsInt](x: T, y: T, out: FSharpRef[T] | None = None) -> T | tuple[T, T]:
26+
# Rust wrapper types already use truncated division and remainder
27+
q = x // y # type: ignore[operator]
28+
r = x % y # type: ignore[operator]
29+
if out is None:
30+
return (q, r)
31+
out.contents = r
32+
return q
33+
34+
1535
def op_unary_negation_int8(x: int8) -> int8:
1636
return -x
1737

@@ -26,6 +46,7 @@ def op_unary_negation_int32(x: int32) -> int32:
2646

2747
__all__ = [
2848
"AllowHexSpecifier",
49+
"div_rem",
2950
"get_range",
3051
"op_unary_negation_int8",
3152
"op_unary_negation_int16",

src/fable-library-py/fable_library/long.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Any, Literal, SupportsFloat, SupportsInt, overload
22

3-
from .core import float64, int32, int64, uint64
3+
from .core import FSharpRef, float64, int32, int64, uint64
44
from .core._core import get_range_64 as get_range
55
from .core._core import parse_int64 as parse
66
from .core._core import try_parse_int64 as try_parse
@@ -127,6 +127,24 @@ def op_modulus(a: int64 | uint64, b: int64 | uint64) -> int64 | uint64:
127127
return a % b
128128

129129

130+
@overload
131+
def div_rem(x: int64, y: int64) -> tuple[int64, int64]: ...
132+
133+
134+
@overload
135+
def div_rem(x: int64, y: int64, out: FSharpRef[int64]) -> int64: ...
136+
137+
138+
def div_rem(x: int64, y: int64, out: FSharpRef[int64] | None = None) -> int64 | tuple[int64, int64]:
139+
# Rust wrapper types already use truncated division and remainder
140+
q = x // y
141+
r = x % y
142+
if out is None:
143+
return (q, r)
144+
out.contents = r
145+
return q
146+
147+
130148
@overload
131149
def op_right_shift(a: int64, b: int32) -> int64: ...
132150

@@ -278,6 +296,7 @@ def to_string(x: int64 | uint64) -> str:
278296

279297
__all__ = [
280298
"compare",
299+
"div_rem",
281300
"from_bits",
282301
"from_int",
283302
"from_integer",

tests/Python/TestArithmetic.fs

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -66,36 +66,36 @@ let ``test Infix modulo with negative numbers`` () =
6666
5 % -3 |> equal 2
6767
-5 % -3 |> equal -2
6868

69-
// [<Fact>]
70-
// let ``test Math.DivRem works with bytes`` () =
71-
// Math.DivRem(5y, 2y) |> equal struct (2y, 1y)
72-
// Math.DivRem(4y, 2y) |> equal struct (2y, 0y)
69+
[<Fact>]
70+
let ``test Math.DivRem works with bytes`` () =
71+
Math.DivRem(5y, 2y) |> equal struct (2y, 1y)
72+
Math.DivRem(4y, 2y) |> equal struct (2y, 0y)
7373

74-
// [<Fact>]
75-
// let ``test Math.DivRem works with ints`` () =
76-
// Math.DivRem(5, 2) |> equal struct (2, 1)
77-
// Math.DivRem(4, 2) |> equal struct (2, 0)
74+
[<Fact>]
75+
let ``test Math.DivRem works with ints`` () =
76+
Math.DivRem(5, 2) |> equal struct (2, 1)
77+
Math.DivRem(4, 2) |> equal struct (2, 0)
7878

79-
// [<Fact>]
80-
// let ``test Math.DivRem works with longs`` () =
81-
// Math.DivRem(5L, 2L) |> equal struct (2L, 1L)
82-
// Math.DivRem(4L, 2L) |> equal struct (2L, 0L)
79+
[<Fact>]
80+
let ``test Math.DivRem works with longs`` () =
81+
Math.DivRem(5L, 2L) |> equal struct (2L, 1L)
82+
Math.DivRem(4L, 2L) |> equal struct (2L, 0L)
8383

84-
// [<Fact>]
85-
// let ``test Math.DivRem works with ints and outref`` () =
86-
// let mutable rem = -1
87-
// Math.DivRem(5, 2, &rem) |> equal 2
88-
// rem |> equal 1
89-
// Math.DivRem(4, 2, &rem) |> equal 2
90-
// rem |> equal 0
84+
[<Fact>]
85+
let ``test Math.DivRem works with ints and outref`` () =
86+
let mutable rem = -1
87+
Math.DivRem(5, 2, &rem) |> equal 2
88+
rem |> equal 1
89+
Math.DivRem(4, 2, &rem) |> equal 2
90+
rem |> equal 0
9191

92-
// [<Fact>]
93-
// let ``test Math.DivRem works with longs and outref`` () =
94-
// let mutable rem = -1L
95-
// Math.DivRem(5L, 2L, &rem) |> equal 2L
96-
// rem |> equal 1L
97-
// Math.DivRem(4L, 2L, &rem) |> equal 2L
98-
// rem |> equal 0L
92+
[<Fact>]
93+
let ``test Math.DivRem works with longs and outref`` () =
94+
let mutable rem = -1L
95+
Math.DivRem(5L, 2L, &rem) |> equal 2L
96+
rem |> equal 1L
97+
Math.DivRem(4L, 2L, &rem) |> equal 2L
98+
rem |> equal 0L
9999

100100
[<Fact>]
101101
let ``test Evaluation order is preserved by generated code`` () =
@@ -464,11 +464,11 @@ let ``test BigInt Infix modulo with negative numbers`` () =
464464
5I % -3I |> equal 2I
465465
-5I % -3I |> equal -2I
466466

467-
// [<Fact>]
468-
// let ``test BigInt.DivRem works`` () = // See #1744
469-
// let quotient,remainder = bigint.DivRem(5I,2I)
470-
// 2I |> equal quotient
471-
// 1I |> equal remainder
467+
[<Fact>]
468+
let ``test BigInt.DivRem works`` () = // See #1744
469+
let quotient,remainder = bigint.DivRem(5I,2I)
470+
2I |> equal quotient
471+
1I |> equal remainder
472472

473473
[<Fact>]
474474
let ``test BigInt Evaluation order is preserved by generated code`` () =

0 commit comments

Comments
 (0)