Skip to content

Commit fd40fb1

Browse files
committed
py/parsenum: Extend mp_parse_num_integer() to parse long long.
If long integer support is 'long long' then mp_parse_num_integer() can parse to it directly instead of failing over from small int. One tricky case this correctly overflows on is things like int("9223372036854775808") which is one more than LLONG_MAX in decimal. No unit test case added for this as it's too hard to detect 64-bit long integer mode. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <angus@redyak.com.au>
1 parent 61eca4c commit fd40fb1

File tree

3 files changed

+37
-52
lines changed

3 files changed

+37
-52
lines changed

py/objint_longlong.c

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@
3939

4040
#if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_LONGLONG
4141

42-
#include <errno.h>
43-
4442
#if MICROPY_PY_SYS_MAXSIZE
4543
// Export value for sys.maxsize
4644
const mp_obj_int_t mp_sys_maxsize_obj = {{&mp_type_int}, MP_SSIZE_MAX};
@@ -300,49 +298,6 @@ mp_obj_t mp_obj_new_int_from_ull(unsigned long long val) {
300298
return mp_obj_new_int_from_ll(val);
301299
}
302300

303-
mp_obj_t mp_obj_new_int_from_str_len(const char **str, size_t len, bool neg, unsigned int base) {
304-
char temp_buf[65]; // Large enough to hold 64-bit base 2 str + NUL terminating byte
305-
const char *head = *str;
306-
char *endptr;
307-
long long parsed;
308-
309-
errno = 0;
310-
parsed = strtoll(head, &endptr, base);
311-
312-
// If 'str' is not properly terminated and has valid digits all through
313-
// and after the buffer, then strtoll will have walked off the end
314-
if (endptr > head + len) {
315-
// Disregard any leading zeroes to ensure the value can fit
316-
while (len >= sizeof(temp_buf) && base != 0 && *head == '0') {
317-
head++;
318-
len--;
319-
}
320-
if (len < sizeof(temp_buf)) {
321-
memcpy(temp_buf, head, len);
322-
temp_buf[len] = 0;
323-
errno = 0;
324-
parsed = strtoll(temp_buf, &endptr, base);
325-
assert(errno != 0 || endptr == temp_buf + len); // The first strtoll() checked all digits are valid
326-
endptr = (char *)head + len;
327-
}
328-
// (if str doesn't fit in temp_buf, will fall through and fail below)
329-
}
330-
331-
if (errno != 0) {
332-
return mp_const_none; // Conversion failed, caller should raise OverflowError
333-
}
334-
335-
if (neg) {
336-
parsed *= -1;
337-
}
338-
339-
mp_obj_t result = mp_obj_new_int_from_ll(parsed);
340-
341-
// If endptr isn't (head + len) then string contained invalid chars, caller should fail
342-
*str = endptr;
343-
return result;
344-
}
345-
346301
mp_int_t mp_obj_int_get_truncated(mp_const_obj_t self_in) {
347302
if (mp_obj_is_small_int(self_in)) {
348303
return MP_OBJ_SMALL_INT_VALUE(self_in);

py/parsenum.c

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,29 @@ static NORETURN void raise_exc(mp_obj_t exc, mp_lexer_t *lex) {
4646
nlr_raise(exc);
4747
}
4848

49+
// mp_parse_num_integer parses a small integer directly, except for the
50+
// special case where bigint support is long long, in which case
51+
// it parses long long and returns either a long long or a small int.
52+
#if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_LONGLONG
53+
typedef mp_int_t parsed_int_t;
54+
55+
#define parsed_int_mul_overflow mp_small_int_mul_overflow
56+
inline static bool parsed_int_fits(parsed_int_t int_val, mp_uint_t dig) {
57+
return MP_SMALL_INT_FITS(int_val + dig);
58+
}
59+
#else
60+
typedef long long parsed_int_t;
61+
62+
#define parsed_int_mul_overflow mp_mul_ll_overflow
63+
64+
inline static bool parsed_int_fits(parsed_int_t int_val, mp_uint_t dig) {
65+
// Unlike MP_SMALL_INT, we can't be assured that adding a digit won't
66+
// trigger an overflow so need to explicitly check
67+
assert(int_val >= 0);
68+
return int_val <= LLONG_MAX - dig;
69+
}
70+
#endif
71+
4972
mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, mp_lexer_t *lex) {
5073
const byte *restrict str = (const byte *)str_;
5174
const byte *restrict top = str + len;
@@ -76,7 +99,7 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m
7699
str += mp_parse_num_base((const char *)str, top - str, &base);
77100

78101
// string should be an integer number
79-
mp_int_t int_val = 0;
102+
parsed_int_t int_val = 0;
80103
const byte *restrict str_val_start = str;
81104
for (; str < top; str++) {
82105
// get next digit as a value
@@ -99,24 +122,27 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m
99122
}
100123

101124
// add next digi and check for overflow
102-
if (mp_small_int_mul_overflow(int_val, base, &int_val)) {
125+
if (parsed_int_mul_overflow(int_val, base, &int_val)) {
103126
goto overflow;
104127
}
105-
int_val += dig;
106-
if (!MP_SMALL_INT_FITS(int_val)) {
128+
if (!parsed_int_fits(int_val, dig)) {
107129
goto overflow;
108130
}
131+
int_val += dig;
109132
}
110133

111134
// negate value if needed
112135
if (neg) {
113136
int_val = -int_val;
114137
}
115138

116-
// create the small int
139+
#if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_LONGLONG
140+
ret_val = mp_obj_new_int_from_ll(int_val); // Could be large or small int
141+
#else
117142
ret_val = MP_OBJ_NEW_SMALL_INT(int_val);
118-
119143
have_ret_val:
144+
#endif
145+
120146
// check we parsed something
121147
if (str == str_val_start) {
122148
goto value_error;
@@ -135,13 +161,17 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m
135161
return ret_val;
136162

137163
overflow:
164+
#if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_LONGLONG
138165
// reparse using long int
139166
{
140167
const char *s2 = (const char *)str_val_start;
141168
ret_val = mp_obj_new_int_from_str_len(&s2, top - str_val_start, neg, base);
142169
str = (const byte *)s2;
143170
goto have_ret_val;
144171
}
172+
#else
173+
mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("result overflows long long storage"));
174+
#endif
145175

146176
value_error:
147177
{

tests/extmod/uctypes_array_load_store.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# 'int' needs to be able to represent UINT64 for this test
1010
try:
1111
int("FF" * 8, 16)
12-
except ValueError:
12+
except OverflowError:
1313
print("SKIP")
1414
raise SystemExit
1515

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy