From 98caf169b760deb6ca01668bbfb519cc309fbaf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lozier?= Date: Mon, 2 Jan 2023 17:11:36 -0500 Subject: [PATCH] Fix formatting of int as float with precision --- .../Runtime/Operations/BigIntegerOps.cs | 30 +++++-- Src/IronPython/Runtime/Operations/IntOps.cs | 26 ++++-- Tests/test_strformat.py | 79 +++++++++---------- 3 files changed, 81 insertions(+), 54 deletions(-) diff --git a/Src/IronPython/Runtime/Operations/BigIntegerOps.cs b/Src/IronPython/Runtime/Operations/BigIntegerOps.cs index 76e001649..229be98eb 100644 --- a/Src/IronPython/Runtime/Operations/BigIntegerOps.cs +++ b/Src/IronPython/Runtime/Operations/BigIntegerOps.cs @@ -738,16 +738,15 @@ public static BigInteger ToBigInteger(BigInteger self) { public static string/*!*/ __format__(CodeContext/*!*/ context, BigInteger/*!*/ self, [NotNone] string/*!*/ formatSpec) { StringFormatSpec spec = StringFormatSpec.FromString(formatSpec); - if (spec.Precision != null) { - throw PythonOps.ValueError("Precision not allowed in integer format specifier"); - } - BigInteger val = self.Abs(); string digits; switch (spec.Type) { case 'n': + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } CultureInfo culture = context.LanguageContext.NumericCulture; if (culture == CultureInfo.InvariantCulture) { @@ -765,6 +764,9 @@ public static BigInteger ToBigInteger(BigInteger self) { break; case null: case 'd': + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } if (spec.ThousandsComma || spec.ThousandsUnderscore) { var numberFormat = spec.ThousandsUnderscore ? FormattingHelper.InvariantUnderscoreNumberInfo : CultureInfo.InvariantCulture.NumberFormat; @@ -790,22 +792,38 @@ public static BigInteger ToBigInteger(BigInteger self) { digits = DoubleOps.DoubleToFormatString(context, ToDouble(val), spec); break; case 'X': + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } digits = AbsToHex(val, lowercase: false); break; case 'x': + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } digits = AbsToHex(val, lowercase: true); break; case 'o': // octal + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } digits = ToOctal(val, lowercase: true); break; case 'b': // binary + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } digits = ToBinary(val, includeType: false, lowercase: true); break; case 'c': // single char - int iVal; + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } if (spec.Sign != null) { throw PythonOps.ValueError("Sign not allowed with integer format specifier 'c'"); - } else if (!self.AsInt32(out iVal)) { + } + int iVal; + if (!self.AsInt32(out iVal)) { throw PythonOps.OverflowError("Python int too large to convert to System.Int32"); } else if(iVal < 0 || iVal > 0x10ffff) { throw PythonOps.OverflowError("%c arg not in range(0x110000)"); diff --git a/Src/IronPython/Runtime/Operations/IntOps.cs b/Src/IronPython/Runtime/Operations/IntOps.cs index 5edf33255..075e0bd41 100644 --- a/Src/IronPython/Runtime/Operations/IntOps.cs +++ b/Src/IronPython/Runtime/Operations/IntOps.cs @@ -208,16 +208,15 @@ public static string __format__(CodeContext/*!*/ context, int self, [NotNone] st StringFormatSpec spec = StringFormatSpec.FromString(formatSpec); - if (spec.Precision != null) { - throw PythonOps.ValueError("Precision not allowed in integer format specifier"); - } - int val = Math.Abs(self); string digits; switch (spec.Type) { case 'n': + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } CultureInfo culture = context.LanguageContext.NumericCulture; if (culture == CultureInfo.InvariantCulture) { @@ -237,6 +236,9 @@ public static string __format__(CodeContext/*!*/ context, int self, [NotNone] st break; case null: case 'd': + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } if (spec.ThousandsComma || spec.ThousandsUnderscore) { var numberFormat = spec.ThousandsUnderscore ? FormattingHelper.InvariantUnderscoreNumberInfo : CultureInfo.InvariantCulture.NumberFormat; @@ -262,22 +264,36 @@ public static string __format__(CodeContext/*!*/ context, int self, [NotNone] st digits = DoubleOps.DoubleToFormatString(context, val, spec); break; case 'X': + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } digits = ToHex(val, lowercase: false); break; case 'x': + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } digits = ToHex(val, lowercase: true); break; case 'o': // octal + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } digits = ToOctal(val, lowercase: true); break; case 'b': // binary + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } digits = ToBinary(val, includeType: false); break; case 'c': // single char + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } if (spec.Sign != null) { throw PythonOps.ValueError("Sign not allowed with integer format specifier 'c'"); } - if (self < 0 || self > 0x10ffff) { throw PythonOps.OverflowError("%c arg not in range(0x110000)"); } diff --git a/Tests/test_strformat.py b/Tests/test_strformat.py index 9f2d5a0f0..80a4fdd4c 100644 --- a/Tests/test_strformat.py +++ b/Tests/test_strformat.py @@ -402,49 +402,26 @@ def test_float___format__(self): (111230.54, '.2e', '1.11e+05'), (111230.54, '.1e', '1.1e+05'), - (23.0, 'E', '2.300000E+01'), - (23.0, '.6E', '2.300000E+01'), - (23.0, '.0E', '2E+01'), - (23.0, '.1E', '2.3E+01'), - (23.0, '.2E', '2.30E+01'), - (23.0, '.3E', '2.300E+01'), - (23.0, '.4E', '2.3000E+01'), - (230.0, '.2E', '2.30E+02'), - (230.1, '.2E', '2.30E+02'), - (230.5, '.3E', '2.305E+02'), - (230.5, '.4E', '2.3050E+02'), - (230.54, '.4E', '2.3054E+02'), - (230.54, '.5E', '2.30540E+02'), - (1230.54, '.5E', '1.23054E+03'), - (11230.54, '.5E', '1.12305E+04'), - (111230.54, '.5E', '1.11231E+05'), - (111230.54, '.4E', '1.1123E+05'), - (111230.54, '.3E', '1.112E+05'), - (111230.54, '.2E', '1.11E+05'), - (111230.54, '.1E', '1.1E+05'), - - (23.0, 'F', '23.000000'), - (23.0, '.6F', '23.000000'), - (23.0, '.0F', '23'), - (23.0, '.1F', '23.0'), - (23.0, '.2F', '23.00'), - (23.0, '.3F', '23.000'), - (23.0, '.4F', '23.0000'), - (230.0, '.2F', '230.00'), - (230.1, '.2F', '230.10'), - (230.5, '.3F', '230.500'), - (230.5, '.4F', '230.5000'), - (230.54, '.4F', '230.5400'), - (230.54, '.5F', '230.54000'), - (1230.54, '.5F', '1230.54000'), - (11230.54, '.5F', '11230.54000'), - (111230.54, '.5F', '111230.54000'), - (111230.54, '.4F', '111230.5400'), - (111230.54, '.3F', '111230.540'), - (111230.54, '.2F', '111230.54'), - (111230.54, '.1F', '111230.5'), - (111230.55, '.1F', '111230.6'), - (-111230.55, '.1F', '-111230.6'), + (23.0, 'f', '23.000000'), + (23.0, '.6f', '23.000000'), + (23.0, '.0f', '23'), + (23.0, '.1f', '23.0'), + (23.0, '.2f', '23.00'), + (23.0, '.3f', '23.000'), + (23.0, '.4f', '23.0000'), + (230.0, '.2f', '230.00'), + (230.1, '.2f', '230.10'), + (230.5, '.3f', '230.500'), + (230.5, '.4f', '230.5000'), + (230.54, '.4f', '230.5400'), + (230.54, '.5f', '230.54000'), + (1230.54, '.5f', '1230.54000'), + (11230.54, '.5f', '11230.54000'), + (111230.54, '.5f', '111230.54000'), + (111230.54, '.4f', '111230.5400'), + (111230.54, '.3f', '111230.540'), + (111230.54, '.2f', '111230.54'), + (111230.54, '.1f', '111230.5'), (111230.55, '.1f', '111230.6'), (-111230.55, '.1f', '-111230.6'), @@ -543,11 +520,18 @@ def test_float___format__(self): (10e667/10e667, '', 'nan'), ] + upper_spec_trans = str.maketrans({"f": "F", "e": "E", "g": "G"}) for value, spec, result in tests: actual = value.__format__(spec) self.assertEqual(actual, result, "value:{0}, spec:{1}, (expected) result:{2}, actual:{3}, expr:({0}).__format__('{1}')".format(value, spec, result, actual)) + # upper-case version + upper_spec = spec.translate(upper_spec_trans) + if spec != upper_spec: + actual = value.__format__(upper_spec) + self.assertEqual(actual, result.upper(), "value:{0}, spec:{1}, (expected) result:{2}, actual:{3}, expr:({0}).__format__('{1}')".format(value, upper_spec, result.upper(), actual)) + if is_netcoreapp21: return # https://github.com/IronLanguages/ironpython3/issues/751 # check locale specific formatting @@ -874,6 +858,15 @@ def test_int___format__(self): (-1000, '+08,d', '-001,000'), (-1000, '-08,d', '-001,000'), (-1000, ' 08,d', '-001,000'), + + # float format with precision - https://github.com/IronLanguages/ironpython3/issues/1629 + (1, '.0%', '100%'), + (1, '.0e', '1e+00'), + (1, '.0E', '1E+00'), + (1, '.0f', '1'), + (1, '.0F', '1'), + (1, '.0g', '1'), + (1, '.0G', '1'), ] for value, spec, result in tests: