Skip to content

Commit 4e6d683

Browse files
authored
[mypyc] Document native floats and integers (#14927)
These were undocumented previously.
1 parent aa2679b commit 4e6d683

File tree

4 files changed

+221
-17
lines changed

4 files changed

+221
-17
lines changed

Diff for: mypyc/doc/float_operations.rst

+28-6
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,40 @@ These ``float`` operations have fast, optimized implementations. Other
77
floating point operations use generic implementations that are often
88
slower.
99

10-
.. note::
11-
12-
At the moment, only a few float operations are optimized. This will
13-
improve in future mypyc releases.
14-
1510
Construction
1611
------------
1712

1813
* Float literal
19-
* ``float(string)``
14+
* ``float(x: int)``
15+
* ``float(x: i64)``
16+
* ``float(x: i32)``
17+
* ``float(x: str)``
18+
* ``float(x: float)`` (no-op)
19+
20+
Operators
21+
---------
22+
23+
* Arithmetic (``+``, ``-``, ``*``, ``/``, ``//``, ``%``)
24+
* Comparisons (``==``, ``!=``, ``<``, etc.)
25+
* Augmented assignment (``x += y``, etc.)
2026

2127
Functions
2228
---------
2329

30+
* ``int(f)``
31+
* ``i32(f)`` (convert to ``i32``)
32+
* ``i64(f)`` (convert to ``i64``)
2433
* ``abs(f)``
34+
* ``math.sin(f)``
35+
* ``math.cos(f)``
36+
* ``math.tan(f)``
37+
* ``math.sqrt(f)``
38+
* ``math.exp(f)``
39+
* ``math.log(f)``
40+
* ``math.floor(f)``
41+
* ``math.ceil(f)``
42+
* ``math.fabs(f)``
43+
* ``math.pow(x, y)``
44+
* ``math.copysign(x, y)``
45+
* ``math.isinf(f)``
46+
* ``math.isnan(f)``

Diff for: mypyc/doc/int_operations.rst

+106-5
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,133 @@
33
Native integer operations
44
=========================
55

6-
Operations on ``int`` values that are listed here have fast, optimized
6+
Mypyc supports these integer types:
7+
8+
* ``int`` (arbitrary-precision integer)
9+
* ``i64`` (64-bit signed integer)
10+
* ``i32`` (32-bit signed integer)
11+
12+
``i64`` and ``i32`` are *native integer types* and must be imported
13+
from the ``mypy_extensions`` module. ``int`` corresponds to the Python
14+
``int`` type, but uses a more efficient runtime representation (tagged
15+
pointer). Native integer types are value types. All integer types have
16+
optimized primitive operations, but the native integer types are more
17+
efficient than ``int``, since they don't require range or bounds
18+
checks.
19+
20+
Operations on integers that are listed here have fast, optimized
721
implementations. Other integer operations use generic implementations
8-
that are often slower. Some operations involving integers and other
9-
types are documented elsewhere, such as list indexing.
22+
that are generally slower. Some operations involving integers and other
23+
types, such as list indexing, are documented elsewhere.
1024

1125
Construction
1226
------------
1327

28+
``int`` type:
29+
1430
* Integer literal
1531
* ``int(x: float)``
32+
* ``int(x: i64)``
33+
* ``int(x: i32)``
1634
* ``int(x: str)``
1735
* ``int(x: str, base: int)``
36+
* ``int(x: int)`` (no-op)
37+
38+
``i64`` type:
39+
40+
* ``i64(x: int)``
41+
* ``i64(x: float)``
42+
* ``i64(x: i32)``
43+
* ``i64(x: str)``
44+
* ``i64(x: str, base: int)``
45+
* ``i64(x: i64)`` (no-op)
46+
47+
``i32`` type:
48+
49+
* ``i32(x: int)``
50+
* ``i32(x: float)``
51+
* ``i32(x: i64)`` (truncate)
52+
* ``i32(x: str)``
53+
* ``i32(x: str, base: int)``
54+
* ``i32(x: i32)`` (no-op)
55+
56+
Conversions from ``int`` to a native integer type raise
57+
``OverflowError`` if the value is too large or small. Conversions from
58+
a wider native integer type to a narrower one truncate the value and never
59+
fail. More generally, operations between native integer types don't
60+
check for overflow.
61+
62+
Implicit conversions
63+
--------------------
64+
65+
``int`` values can be implicitly converted to a native integer type,
66+
for convenience. This means that these are equivalent::
67+
68+
def implicit() -> None:
69+
# Implicit conversion of 0 (int) to i64
70+
x: i64 = 0
71+
72+
def explicit() -> None:
73+
# Explicit conversion of 0 (int) to i64
74+
x = i64(0)
75+
76+
Similarly, a native integer value can be implicitly converted to an
77+
arbitrary-precision integer. These two functions are equivalent::
78+
79+
def implicit(x: i64) -> int:
80+
# Implicit conversion from i64 to int
81+
return x
82+
83+
def explicit(x: i64) -> int:
84+
# Explicit conversion from i64 to int
85+
return int(x)
1886

1987
Operators
2088
---------
2189

22-
* Arithmetic (``+``, ``-``, ``*``, ``//``, ``%``)
90+
* Arithmetic (``+``, ``-``, ``*``, ``//``, ``/``, ``%``)
2391
* Bitwise operations (``&``, ``|``, ``^``, ``<<``, ``>>``, ``~``)
2492
* Comparisons (``==``, ``!=``, ``<``, etc.)
2593
* Augmented assignment (``x += y``, etc.)
2694

95+
If one of the above native integer operations overflows or underflows,
96+
the behavior is undefined. Native integer types should only be used if
97+
all possible values are small enough for the type. For this reason,
98+
the arbitrary-precision ``int`` type is recommended unless the
99+
performance of integer operations is critical.
100+
101+
It's a compile-time error to mix different native integer types in a
102+
binary operation such as addition. An explicit conversion is required::
103+
104+
def add(x: i64, y: i32) -> None:
105+
a = x + y # Error (i64 + i32)
106+
b = x + i64(y) # OK
107+
108+
You can freely mix a native integer value and an arbitrary-precision
109+
``int`` value in an operation. The native integer type is "sticky"
110+
and the ``int`` operand is coerced to the native integer type::
111+
112+
def example(x: i64, y: int) -> None:
113+
a = x * y
114+
# Type of "a" is "i64"
115+
...
116+
b = 1 - x
117+
# Similarly, type of "b" is "i64"
118+
27119
Statements
28120
----------
29121

30-
For loop over range:
122+
For loop over a range is compiled efficiently, if the ``range(...)`` object
123+
is constructed in the for statement (after ``in``):
31124

32125
* ``for x in range(end)``
33126
* ``for x in range(start, end)``
34127
* ``for x in range(start, end, step)``
128+
129+
If one of the arguments to ``range`` in a for loop is a native integer
130+
type, the type of the loop variable is inferred to have this native
131+
integer type, instead of ``int``::
132+
133+
for x in range(i64(n)):
134+
# Type of "x" is "i64"
135+
...

Diff for: mypyc/doc/performance_tips_and_tricks.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,6 @@ These things also tend to be relatively slow:
103103

104104
* Using generator functions
105105

106-
* Using floating point numbers (they are relatively unoptimized)
107-
108106
* Using callable values (i.e. not leveraging early binding to call
109107
functions or methods)
110108

@@ -160,6 +158,8 @@ Here are examples of features that are fast, in no particular order
160158

161159
* Many integer operations
162160

161+
* Many ``float`` operations
162+
163163
* Booleans
164164

165165
* :ref:`Native list operations <list-ops>`, such as indexing,

Diff for: mypyc/doc/using_type_annotations.rst

+85-4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ mypyc, and many operations on these types have efficient
3030
implementations:
3131

3232
* ``int`` (:ref:`native operations <int-ops>`)
33+
* ``i64`` (:ref:`documentation <native-ints>`, :ref:`native operations <int-ops>`)
34+
* ``i32`` (:ref:`documentation <native-ints>`, :ref:`native operations <int-ops>`)
3335
* ``float`` (:ref:`native operations <float-ops>`)
3436
* ``bool`` (:ref:`native operations <bool-ops>`)
3537
* ``str`` (:ref:`native operations <str-ops>`)
@@ -271,7 +273,8 @@ Value and heap types
271273
In CPython, memory for all objects is dynamically allocated on the
272274
heap. All Python types are thus *heap types*. In compiled code, some
273275
types are *value types* -- no object is (necessarily) allocated on the
274-
heap. ``bool``, ``None`` and fixed-length tuples are value types.
276+
heap. ``bool``, ``float``, ``None``, :ref:`native integer types <native-ints>`
277+
and fixed-length tuples are value types.
275278

276279
``int`` is a hybrid. For typical integer values, it is a value
277280
type. Large enough integer values, those that require more than 63
@@ -287,9 +290,9 @@ Value types have a few differences from heap types:
287290
* Similarly, mypyc transparently changes from a heap-based
288291
representation to a value representation (unboxing).
289292

290-
* Object identity of integers and tuples is not preserved. You should
291-
use ``==`` instead of ``is`` if you are comparing two integers or
292-
fixed-length tuples.
293+
* Object identity of integers, floating point values and tuples is not
294+
preserved. You should use ``==`` instead of ``is`` if you are comparing
295+
two integers, floats or fixed-length tuples.
293296

294297
* When an instance of a subclass of a value type is converted to the
295298
base type, it is implicitly converted to an instance of the target
@@ -312,3 +315,81 @@ Example::
312315
x = a[0]
313316
# True is converted to 1 on assignment
314317
x = True
318+
319+
Since integers and floating point values have a different runtime
320+
representations and neither can represent all the values of the other
321+
type, type narrowing of floating point values through assignment is
322+
disallowed in compiled code. For consistency, mypyc rejects assigning
323+
an integer value to a float variable even in variable initialization.
324+
An explicit conversion is required.
325+
326+
Examples::
327+
328+
def narrowing(n: int) -> None:
329+
# Error: Incompatible value representations in assignment
330+
# (expression has type "int", variable has type "float")
331+
x: float = 0
332+
333+
y: float = 0.0 # Ok
334+
335+
if f():
336+
y = n # Error
337+
if f():
338+
y = float(n) # Ok
339+
340+
.. _native-ints:
341+
342+
Native integer types
343+
--------------------
344+
345+
You can use the native integer types ``i64`` (64-bit signed integer)
346+
and ``i32`` (32-bit signed integer) if you know that integer values
347+
will always fit within fixed bounds. These types are faster than the
348+
arbitrary-precision ``int`` type, since they don't require overflow
349+
checks on operations. ``i32`` may also use less memory than ``int``
350+
values. The types are imported from the ``mypy_extensions`` module
351+
(installed via ``pip install mypy_extensions``).
352+
353+
Example::
354+
355+
from mypy_extensions import i64
356+
357+
def sum_list(l: list[i64]) -> i64:
358+
s: i64 = 0
359+
for n in l:
360+
s += n
361+
return s
362+
363+
# Implicit conversions from int to i64
364+
print(sum_list([1, 3, 5]))
365+
366+
.. note::
367+
368+
Since there are no overflow checks when performing native integer
369+
arithmetic, the above function could result in an overflow or other
370+
undefined behavior if the sum might not fit within 64 bits.
371+
372+
The behavior when running as interpreted Python program will be
373+
different if there are overflows. Declaring native integer types
374+
have no effect unless code is compiled. Native integer types are
375+
effectively equivalent to ``int`` when interpreted.
376+
377+
Native integer types have these additional properties:
378+
379+
* Values can be implicitly converted between ``int`` and a native
380+
integer type (both ways).
381+
382+
* Conversions between different native integer types must be explicit.
383+
A conversion to a narrower native integer type truncates the value
384+
without a runtime overflow check.
385+
386+
* If a binary operation (such as ``+``) or an augmented assignment
387+
(such as ``+=``) mixes native integer and ``int`` values, the
388+
``int`` operand is implicitly coerced to the native integer type
389+
(native integer types are "sticky").
390+
391+
* You can't mix different native integer types in binary
392+
operations. Instead, convert between types explicitly.
393+
394+
For more information about native integer types, refer to
395+
:ref:`native integer operations <int-ops>`.

0 commit comments

Comments
 (0)