Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 41 additions & 19 deletions gmp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1808,41 +1808,63 @@ calc_number_widths(bool negative, size_t len, const IntFormatSpec *format,
spec->sign = '-';
}
}

Py_ssize_t n = spec->n_digits;
Py_ssize_t m = format->group;
Py_ssize_t w = format->width - spec->n_sign - spec->n_prefix;

if (format->group) {
/* Compute number of grouped digits. If no zero-padding is needed,
i.e. n digits being grouped in m digits, then the number of
separators is obviously ⌊(n - 1)/m⌋.

The tricky part is computing number of leading zeros, if required.
Let now w - requested width of output, without sign and prefix,
and n - number of digits in the number.
Then if x = ⌈(w*m + 1)/(m + 1)⌉ > n - it's the required number
of digits, including zero-padding.

Indeed, w - x - ⌊(x - 1)/m⌋ = w - ⌊((m + 1)*x - 1)/m⌋.
Now (positive m) use identity ⌈n/m⌉ = ⌊(n - 1)/m⌋ + 1:
= w - ⌊((m+1)*⌊(w*m)/(m + 1)⌋ + m)/m⌋ =
= ⌊(m + 1)/m*{(w*m)/(m + 1)} - 1⌋. That's equal either to
0 or -1, because -1 ≤ (m + 1)/m*{(w*m)/(m + 1)} - 1 < 1/m < 1.

In other words, actual width of output will be either w or
one digit bigger, as in examples below:

>>> format(123, '011_d')
'000_000_123'
>>> format(123, '012_d')
'0_000_000_123'
*/
if (format->zeropad) {
spec->n_leading_zeros = Py_MAX(0,
(format->group*(format->width
- spec->n_sign
- spec->n_prefix + 1)
+ 1)/(format->group + 1)
- spec->n_digits);
spec->n_digits += spec->n_leading_zeros;
spec->n_leading_zeros = Py_MAX(0, (m*(w + 1) + 1)/(m + 1) - n);
n += spec->n_leading_zeros;
spec->n_digits = n;
}
spec->n_grouped_digits = spec->n_digits + (spec->n_digits
- 1)/format->group;
spec->n_grouped_digits = n + (n - 1)/m;
}

w -= spec->n_grouped_digits;
assert(!spec->n_leading_zeros || w == 0 || w == -1);
/* Given the desired width and the total of digit and non-digit
space we consume, see if we need any padding. format->width can
be zero (meaning no padding). */
Py_ssize_t n_padding = format->width - (spec->n_sign + spec->n_prefix
+ spec->n_grouped_digits);

if (n_padding > 0) {
if (w > 0) {
switch (format->align) {
case '<':
spec->n_rpadding = n_padding;
spec->n_rpadding = w;
break;
case '^':
spec->n_lpadding = n_padding / 2;
spec->n_rpadding = n_padding - spec->n_lpadding;
spec->n_lpadding = w / 2;
spec->n_rpadding = w - spec->n_lpadding;
break;
case '=':
spec->n_spadding = n_padding;
spec->n_spadding = w;
break;
case '>':
default:
spec->n_lpadding = n_padding;
spec->n_lpadding = w;
break;
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def test_lcm_nary(xs):
@example(1, 9727076909039105, -48, 53, "u")
@example(1, 6277101735386680763495507056286727952638980837032266301441,
0, 64, "f")
def test_mpmath_normalize(sign, man, exp, prec, rnd):
def test__mpmath_normalize(sign, man, exp, prec, rnd):
mman = mpz(man)
sign = int(sign)
bc = mman.bit_length()
Expand All @@ -152,7 +152,7 @@ def test_mpmath_normalize(sign, man, exp, prec, rnd):
sampled_from(["n", "f", "c", "u", "d"]))
@example(-6277101735386680763495507056286727952638980837032266301441,
0, 64, "f")
def test_mpmath_create(man, exp, prec, rnd):
def test__mpmath_create(man, exp, prec, rnd):
mman = mpz(man)
res = mpmath_from_man_exp(man, exp, prec, rnd)
assert all(type(_) is int for _ in res)
Expand Down
37 changes: 13 additions & 24 deletions tests/test_mpz.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ def test_mpz_interface():
assert mpz("१23") == 123
assert mpz("\t123") == 123
if platform.python_implementation() != "GraalVM":
assert mpz("\xa0123") == 123 # see oracle/graalpython#1209
assert mpz("\xa0123") == 123 # oracle/graalpython#1209
assert mpz("-010") == -10
assert mpz("-10") == -10
assert mpz("0b_10", 0) == 2
Expand Down Expand Up @@ -414,7 +414,7 @@ def test_mul_distributivity(x, y, z):
@example(-2, 1<<64)
@example(2, 1<<64)
@example(18446744073709551615<<64, -1<<64)
@example(int("0x"+"f"*32, 0), -1<<64) # XXX: assuming BITS_PER_DIGIT == 64
@example((1<<128) - 1, -1<<64) # assuming BITS_PER_DIGIT == 64
@example(-68501870735943706700000000000000000001, 10**20) # issue 117
@example(0, 123)
@example(0, -1382074480823709287)
Expand Down Expand Up @@ -627,7 +627,7 @@ def test_power_mod(x, y, z):
assert pow(mx, y, z) == r
assert pow(x, my, z) == r
if platform.python_implementation() == "PyPy":
return # XXX: pypy/pypy#5207
return # pypy/pypy#5207
assert pow(x, y, mz) == r


Expand Down Expand Up @@ -660,7 +660,7 @@ def test_power_errors():
mpz(1) << ((1 << 64) - 1)


@given(bigints(), integers(max_value=12345))
@given(bigints(), integers(min_value=0, max_value=12345))
@example(18446744073709551618, 64)
@example(1, 1<<128)
@example(90605555449081991889354259339521952450308780844225461, 64)
Expand All @@ -676,29 +676,19 @@ def test_lshift(x, y):
x << my
with pytest.raises(OverflowError):
mx << y
except ValueError:
with pytest.raises(ValueError, match="negative shift count"):
mx << my
with pytest.raises(ValueError, match="negative shift count"):
x << my
with pytest.raises(ValueError, match="negative shift count"):
mx << y
else:
assert mx << my == r
assert mx << y == r
assert x << my == r


@given(bigints(), bigints())
@given(bigints(), bigints(min_value=0))
@example(1, 1<<78)
@example(-1, 1<<128)
@example(-340282366920938463444927863358058659840, 64)
@example(-514220174162876888173427869549172032807104958010493707296440352, 206)
@example(-6277101735386680763495507056286727952638980837032266301441, 128)
def test_rshift(x, y):
# XXX: mp_size_t might be smaller than mp_limb_t
if abs(y) >= 2**32 and platform.system() == "Windows":
return
mx = mpz(x)
my = mpz(y)
try:
Expand All @@ -710,19 +700,19 @@ def test_rshift(x, y):
x >> my
with pytest.raises(OverflowError):
mx >> y
except ValueError:
with pytest.raises(ValueError, match="negative shift count"):
mx >> my
with pytest.raises(ValueError, match="negative shift count"):
x >> my
with pytest.raises(ValueError, match="negative shift count"):
mx >> y
else:
assert mx >> my == r
assert mx >> y == r
assert x >> my == r


def test_shift_errors():
with pytest.raises(ValueError, match="negative shift count"):
mpz(123) << mpz(-123)
with pytest.raises(ValueError, match="negative shift count"):
mpz(123) >> mpz(-123)


@given(bigints())
def test_getters(x):
mx = mpz(x)
Expand Down Expand Up @@ -990,7 +980,6 @@ def test_pickle(protocol, x):
assert mx == pickle.loads(pickle.dumps(mx, protocol))


@pytest.mark.skipif(platform.system() == "Darwin", reason="XXX")
@settings(max_examples=100)
@given(lists(integers(min_value=2), min_size=3, max_size=20))
def test_mpz_collatz(xs):
Expand All @@ -1005,7 +994,7 @@ def f(n):


@pytest.mark.skipif(platform.python_implementation() == "GraalVM",
reason="oracle/graalpython#593")
reason="oracle/graalpython#998")
def test_int_api():
for meth in dir(int):
m = getattr(int, meth)
Expand Down
2 changes: 1 addition & 1 deletion utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/* For GraalVM: unicodeobject.h, implicit conversion changes
signedness: 'enum PyUnicode_Kind' to 'int' */
#if defined(__GNUC__) || defined(__clang__)
# pragma GCC diagnostic push /* XXX: oracle/graalpython#580 */
# pragma GCC diagnostic push /* oracle/graalpython#580 */
# pragma GCC diagnostic ignored "-Wsign-conversion"
#endif

Expand Down
Loading