Files
RustPython/extra_tests/snippets/builtin_format.py
Seonghun An 7c6e7dc29e Fix zero padding for string format specs (#8407)
* Fix zero padding for string format specs

Assisted-by: Codex:gpt-5

* Add string alignment format regression test

Assisted-by: Codex:gpt-5
2026-07-29 20:27:59 +09:00

262 lines
9.4 KiB
Python

from testutils import assert_raises
assert format(5, "b") == "101"
assert_raises(TypeError, format, 2, 3, _msg="format called with number")
assert format({}) == "{}"
assert_raises(TypeError, format, {}, "b", _msg="format_spec not empty for dict")
class BadFormat:
def __format__(self, spec):
return 42
assert_raises(TypeError, format, BadFormat())
def test_zero_padding():
i = 1
assert f"{i:04d}" == "0001"
test_zero_padding()
try:
format("result", "=8s")
except ValueError as error:
if str(error) != "'=' alignment not allowed in string format specifier":
raise AssertionError(f"unexpected error message: {error}") from error
else:
raise AssertionError("expected ValueError for '=8s' string format specifier")
assert "{:,}".format(100) == "100"
assert "{:,}".format(1024) == "1,024"
assert "{:_}".format(65536) == "65_536"
assert "{:_}".format(4294967296) == "4_294_967_296"
assert f"{100:_}" == "100"
assert f"{1024:_}" == "1_024"
assert f"{65536:,}" == "65,536"
assert f"{4294967296:,}" == "4,294,967,296"
assert "F" == "{0:{base}}".format(15, base="X")
assert f"{255:#X}" == "0XFF"
assert f"{65:c}" == "A"
assert f"{0x1F5A5:c}" == "🖥"
assert_raises(
ValueError,
"{:+c}".format,
1,
_msg="Sign not allowed with integer format specifier 'c'",
)
assert_raises(
ValueError,
"{:#c}".format,
1,
_msg="Alternate form (#) not allowed with integer format specifier 'c'",
)
assert f"{256:#010x}" == "0x00000100"
assert f"{256:0=#10x}" == "0x00000100"
assert f"{256:0>#10x}" == "000000x100"
assert f"{256:0^#10x}" == "000x100000"
assert f"{256:0<#10x}" == "0x10000000"
assert f"{512:+#010x}" == "+0x0000200"
assert f"{512:0=+#10x}" == "+0x0000200"
assert f"{512:0>+#10x}" == "0000+0x200"
assert f"{512:0^+#10x}" == "00+0x20000"
assert f"{512:0<+#10x}" == "+0x2000000"
assert f"{123:,}" == "123"
assert f"{1234:,}" == "1,234"
assert f"{12345:,}" == "12,345"
assert f"{123456:,}" == "123,456"
assert f"{123:03_}" == "123"
assert f"{123:04_}" == "0_123"
assert f"{123:05_}" == "0_123"
assert f"{123:06_}" == "00_123"
assert f"{123:07_}" == "000_123"
assert f"{255:#010_x}" == "0x000_00ff"
assert f"{255:+#010_x}" == "+0x00_00ff"
assert f"{123.4567:,}" == "123.4567"
assert f"{1234.567:,}" == "1,234.567"
assert f"{12345.67:,}" == "12,345.67"
assert f"{123456.7:,}" == "123,456.7"
assert f"{123.456:07,}" == "123.456"
assert f"{123.456:08,}" == "0,123.456"
assert f"{123.456:09,}" == "0,123.456"
assert f"{123.456:010,}" == "00,123.456"
assert f"{123.456:011,}" == "000,123.456"
assert f"{123.456:+011,}" == "+00,123.456"
assert f"{1234:.3g}" == "1.23e+03"
assert f"{1234567:.6G}" == "1.23457E+06"
assert f"{1234:10}" == " 1234"
assert f"{1234:10,}" == " 1,234"
assert f"{1234:010,}" == "00,001,234"
assert f"{'🐍':4}" == "🐍 "
assert_raises(
ValueError, "{:,o}".format, 1, _msg="ValueError: Cannot specify ',' with 'o'."
)
assert_raises(
ValueError, "{:_n}".format, 1, _msg="ValueError: Cannot specify '_' with 'n'."
)
assert_raises(
ValueError, "{:,o}".format, 1.0, _msg="ValueError: Cannot specify ',' with 'o'."
)
assert_raises(
ValueError, "{:_n}".format, 1.0, _msg="ValueError: Cannot specify '_' with 'n'."
)
assert_raises(
ValueError, "{:,}".format, "abc", _msg="ValueError: Cannot specify ',' with 's'."
)
assert_raises(
ValueError, "{:,x}".format, "abc", _msg="ValueError: Cannot specify ',' with 'x'."
)
assert_raises(
OverflowError,
"{:c}".format,
0x110000,
_msg="OverflowError: %c arg not in range(0x110000)",
)
assert f"{3:f}" == "3.000000"
assert f"{3.1415:.0f}" == "3"
assert f"{3.1415:.1f}" == "3.1"
assert f"{3.1415:.2f}" == "3.14"
assert f"{3.1415:.3f}" == "3.142"
assert f"{3.1415:.4f}" == "3.1415"
assert f"{3.1415:#.0f}" == "3."
assert f"{3.1415:#.1f}" == "3.1"
assert f"{3.1415:#.2f}" == "3.14"
assert f"{3.1415:#.3f}" == "3.142"
assert f"{3.1415:#.4f}" == "3.1415"
assert f"{3:g}" == "3"
assert f"{3.1415:.0g}" == "3"
assert f"{3.1415:.1g}" == "3"
assert f"{3.1415:.2g}" == "3.1"
assert f"{3.1415:.3g}" == "3.14"
assert f"{3.1415:.4g}" == "3.142"
assert f"{0.000012:g}" == "1.2e-05"
assert f"{0.000012:G}" == "1.2E-05"
assert f"{3:#g}" == "3.00000"
assert f"{3.1415:#.0g}" == "3."
assert f"{3.1415:#.1g}" == "3."
assert f"{3.1415:#.2g}" == "3.1"
assert f"{3.1415:#.3g}" == "3.14"
assert f"{3.1415:#.4g}" == "3.142"
assert f"{0.000012:#g}" == "1.20000e-05"
assert f"{0.000012:#G}" == "1.20000E-05"
assert f"{3.1415:.0e}" == "3e+00"
assert f"{3.1415:.1e}" == "3.1e+00"
assert f"{3.1415:.2e}" == "3.14e+00"
assert f"{3.1415:.3e}" == "3.142e+00"
assert f"{3.1415:.4e}" == "3.1415e+00"
assert f"{3.1415:.5e}" == "3.14150e+00"
assert f"{3.1415:.5E}" == "3.14150E+00"
assert f"{3.1415:#.0e}" == "3.e+00"
assert f"{3.1415:#.1e}" == "3.1e+00"
assert f"{3.1415:#.2e}" == "3.14e+00"
assert f"{3.1415:#.3e}" == "3.142e+00"
assert f"{3.1415:#.4e}" == "3.1415e+00"
assert f"{3.1415:#.5e}" == "3.14150e+00"
assert f"{3.1415:#.5E}" == "3.14150E+00"
assert f"{3.1415:.0%}" == "314%"
assert f"{3.1415:.1%}" == "314.2%"
assert f"{3.1415:.2%}" == "314.15%"
assert f"{3.1415:.3%}" == "314.150%"
assert f"{3.1415:#.0%}" == "314.%"
assert f"{3.1415:#.1%}" == "314.2%"
assert f"{3.1415:#.2%}" == "314.15%"
assert f"{3.1415:#.3%}" == "314.150%"
assert f"{3.1415:.0}" == "3e+00"
assert f"{3.1415:.1}" == "3e+00"
assert f"{3.1415:.2}" == "3.1"
assert f"{3.1415:.3}" == "3.14"
assert f"{3.1415:.4}" == "3.142"
assert f"{3.1415:#.0}" == "3.e+00"
assert f"{3.1415:#.1}" == "3.e+00"
assert f"{3.1415:#.2}" == "3.1"
assert f"{3.1415:#.3}" == "3.14"
assert f"{3.1415:#.4}" == "3.142"
assert f"{1234.5:10}" == " 1234.5"
assert f"{1234.5:10,}" == " 1,234.5"
assert f"{1234.5:010,}" == "0,001,234.5"
assert f"{12.34 + 5.6j}" == "(12.34+5.6j)"
assert f"{12.34 - 5.6j: }" == "( 12.34-5.6j)"
assert f"{12.34 + 5.6j:20}" == " (12.34+5.6j)"
assert f"{12.34 + 5.6j:<20}" == "(12.34+5.6j) "
assert f"{-12.34 + 5.6j:^20}" == " (-12.34+5.6j) "
assert f"{12.34 + 5.6j:^+20}" == " (+12.34+5.6j) "
assert f"{12.34 + 5.6j:_^+20}" == "___(+12.34+5.6j)____"
assert f"{-12.34 + 5.6j:f}" == "-12.340000+5.600000j"
assert f"{12.34 + 5.6j:.3f}" == "12.340+5.600j"
assert f"{12.34 + 5.6j:<30.8f}" == "12.34000000+5.60000000j "
assert f"{12.34 + 5.6j:g}" == "12.34+5.6j"
assert f"{12.34 + 5.6j:e}" == "1.234000e+01+5.600000e+00j"
assert f"{12.34 + 5.6j:E}" == "1.234000E+01+5.600000E+00j"
assert f"{12.34 + 5.6j:^30E}" == " 1.234000E+01+5.600000E+00j "
assert f"{12345.6 + 7890.1j:,}" == "(12,345.6+7,890.1j)"
assert f"{12345.6 + 7890.1j:_.3f}" == "12_345.600+7_890.100j"
assert f"{12345.6 + 7890.1j:>+30,f}" == " +12,345.600000+7,890.100000j"
assert f"{123456:,g}" == "123,456"
assert f"{123456:,G}" == "123,456"
assert f"{123456:,e}" == "1.234560e+05"
assert f"{123456:,E}" == "1.234560E+05"
assert f"{123456:,%}" == "12,345,600.000000%"
# test issue 4558
x = 123456789012345678901234567890
for i in range(0, 30):
format(x, ",")
x = x // 10
# Large float precision must not abort the interpreter.
# Previously these paths hit unguarded `format!("{:.*e}", ...)` in
# crates/literal/src/float.rs and `crates/common/src/format.rs` (the `%`
# branch), which panic past Rust's fmt precision limit and killed the
# process instead of raising a Python exception. Internally the limit is
# u16::MAX; output is zero-padded past that boundary to match CPython
# byte-identically.
# Three precision points per format type — below the cap (uncapped
# path), exactly at the cap (boundary), and one past the cap (the
# unhappy case, where internal clamping plus zero-padding has to
# reconstruct CPython's output). All must byte-match CPython.
# f-format pads with trailing zeros up to the requested precision.
assert "{:.65534f}".format(1.5) == "1." + "5" + "0" * 65533 # below cap
assert "{:.65535f}".format(1.5) == "1." + "5" + "0" * 65534 # at cap
assert "{:.65536f}".format(1.5) == "1." + "5" + "0" * 65535 # past cap → padding
# e-format emits a fixed mantissa width + 'e+00'.
assert "{:.65534e}".format(1.5) == "1." + "5" + "0" * 65533 + "e+00" # below
assert "{:.65535e}".format(1.5) == "1." + "5" + "0" * 65534 + "e+00" # at cap
assert (
"{:.65536e}".format(1.5) == "1." + "5" + "0" * 65535 + "e+00"
) # past cap → padding
# %-format multiplies by 100 then applies f-format.
assert "{:.65534%}".format(1.5) == "150." + "0" * 65534 + "%" # below
assert "{:.65535%}".format(1.5) == "150." + "0" * 65535 + "%" # at cap
assert "{:.65536%}".format(1.5) == "150." + "0" * 65536 + "%" # past cap → padding
# g-format strips trailing zeros, so the short form is the natural
# representation regardless of precision.
for p in (65534, 65535, 65536, 1_000_000):
assert ("{:." + str(p) + "g}").format(1.5) == "1.5"
# Far past the cap — verifies the pad path handles arbitrary precision,
# not just one-off values near the boundary.
assert len("{:.1000000f}".format(1.5)) == 1_000_002 # "1." + 1M zeros
assert len("{:.1000000e}".format(1.5)) == 1_000_006 # + "e+00"
assert len("{:.1000000%}".format(1.5)) == 1_000_005 # "150." + 1M zeros + "%"
# Percent overflow: finite input whose *100 is +inf produces "inf%"
# rather than crashing. CPython does the same.
assert "{:.100000%}".format(1.7976931348623157e308) == "inf%"
# Shallow cases unchanged.
assert f"{1.5:.5}" == "1.5"
assert "{:.3f}".format(1.5) == "1.500"
assert "{:.2%}".format(0.25) == "25.00%"
assert "{:.4e}".format(1234.5) == "1.2345e+03"
assert "{:.3g}".format(1234.5) == "1.23e+03"
assert f"{float('nan'):.10f}" == "nan"
assert f"{float('inf'):.10f}" == "inf"