From 8055572d1ba701963f03ea57b8481da3bcf93054 Mon Sep 17 00:00:00 2001 From: Mick Phillips Date: Fri, 2 Feb 2024 21:46:17 -0800 Subject: [PATCH] Port IronLanguages/ironpython3#1643 to ironpython2 for #838. --- Src/IronPython/Runtime/Operations/IntOps.cs | 32 ++++++-- Src/IronPython/Runtime/Operations/LongOps.cs | 63 ++++++++++----- Tests/test_strformat.py | 82 ++++++++++---------- 3 files changed, 105 insertions(+), 72 deletions(-) diff --git a/Src/IronPython/Runtime/Operations/IntOps.cs b/Src/IronPython/Runtime/Operations/IntOps.cs index 1c33a93c8..fdd2a5ab0 100644 --- a/Src/IronPython/Runtime/Operations/IntOps.cs +++ b/Src/IronPython/Runtime/Operations/IntOps.cs @@ -232,7 +232,7 @@ public static object __new__(CodeContext context, PythonType cls) { } #region Binary Operators - + [SpecialName] public static object FloorDivide(int x, int y) { if (y == -1 && x == Int32.MinValue) { @@ -414,15 +414,14 @@ public static object __coerce__(CodeContext context, int x, object o) { public static string __format__(CodeContext/*!*/ context, int self, [NotNull]string/*!*/ formatSpec) { StringFormatSpec spec = StringFormatSpec.FromString(formatSpec); - if (spec.Precision != null) { - throw PythonOps.ValueError("Precision not allowed in integer format specifier"); - } - string digits; int width = 0; 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) { @@ -444,6 +443,9 @@ public static string __format__(CodeContext/*!*/ context, int self, [NotNull]str break; case null: case 'd': + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } if (spec.ThousandsComma) { width = spec.Width ?? 0; @@ -511,22 +513,36 @@ public static string __format__(CodeContext/*!*/ context, int self, [NotNull]str } break; case 'X': + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } digits = ToHex(self, false); break; case 'x': + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } digits = ToHex(self, true); break; case 'o': // octal + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } digits = ToOctal(self, true); break; case 'b': // binary + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } digits = ToBinary(self, 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 > 0xFF) { throw PythonOps.OverflowError("%c arg not in range(0x10000)"); } @@ -588,7 +604,7 @@ internal static string ToBinary(int self) { if (self == Int32.MinValue) { return "-0b10000000000000000000000000000000"; } - + string res = ToBinary(self, true); if (self < 0) { res = "-" + res; @@ -619,7 +635,7 @@ private static string ToBinary(int self, bool includeType) { } else { digits = "10000000000000000000000000000000"; } - + if (includeType) { digits = "0b" + digits; } diff --git a/Src/IronPython/Runtime/Operations/LongOps.cs b/Src/IronPython/Runtime/Operations/LongOps.cs index 40165661e..4fe2f988f 100644 --- a/Src/IronPython/Runtime/Operations/LongOps.cs +++ b/Src/IronPython/Runtime/Operations/LongOps.cs @@ -41,7 +41,7 @@ public static object __new__(CodeContext context, PythonType cls, string s, int public static object __new__(CodeContext/*!*/ context, PythonType cls, IList s) { return __new__(context, cls, s, 10); } - + [StaticExtensionMethod] public static object __new__(CodeContext/*!*/ context, PythonType cls, IList s, int redix) { object value; @@ -86,7 +86,7 @@ public static object __new__(CodeContext context, PythonType cls, object x) { if (x is double) return ReturnObject(context, cls, DoubleOps.__long__((double)x)); if (x is int) return ReturnObject(context, cls, (BigInteger)(int)x); if (x is BigInteger) return ReturnObject(context, cls, x); - + if (x is Complex) throw PythonOps.TypeError("can't convert complex to long; use long(abs(z))"); if (x is decimal) { @@ -331,7 +331,7 @@ public static double TrueDivide([NotNull]BigInteger x, [NotNull]BigInteger y) { // otherwise give the user the truncated result if the result fits in a float BigInteger rem; BigInteger res = BigInteger.DivRem(x, y, out rem); - if (res.TryToFloat64(out fRes)) { + if (res.TryToFloat64(out fRes)) { if(rem != BigInteger.Zero) { // try and figure out the fractional portion BigInteger fraction = y / rem; @@ -343,7 +343,7 @@ public static double TrueDivide([NotNull]BigInteger x, [NotNull]BigInteger y) { } return fRes; - } + } // otherwise report an error throw PythonOps.OverflowError("long/long too large for a float"); @@ -444,7 +444,7 @@ public static string __oct__(BigInteger x) { } public static string __hex__(BigInteger x) { - // CPython 2.5 prints letters in lowercase, with a capital L. + // CPython 2.5 prints letters in lowercase, with a capital L. if (x < 0) { return "-0x" + (-x).ToString(16).ToLower() + "L"; } else { @@ -540,7 +540,7 @@ public static int Compare(BigInteger x, BigInteger y) { [SpecialName] public static int Compare(BigInteger x, int y) { int ix; - if (x.AsInt32(out ix)) { + if (x.AsInt32(out ix)) { return ix == y ? 0 : ix > y ? 1 : -1; } @@ -568,7 +568,7 @@ public static int Compare(BigInteger x, [NotNull]Extensible y) { } [SpecialName] - public static int Compare(BigInteger x, decimal y) { + public static int Compare(BigInteger x, decimal y) { return DecimalOps.__cmp__(x, y); } @@ -604,7 +604,7 @@ public static int __hash__(BigInteger self) { } // Call the DLR's BigInteger hash function, which will return an int32 representation of - // b if b is within the int32 range. We use that as an optimization for hashing, and + // b if b is within the int32 range. We use that as an optimization for hashing, and // assert the assumption below. int hash = self.GetHashCode(); #if DEBUG @@ -637,7 +637,7 @@ public static float ToFloat(BigInteger/*!*/ self) { } #region Binary Ops - + [PythonHidden] public static BigInteger Xor(BigInteger x, BigInteger y) { return x ^ y; @@ -706,7 +706,7 @@ public static bool AsUInt32(BigInteger self, out uint res) { public static bool AsUInt64(BigInteger self, out ulong res) { return self.AsUInt64(out res); } - + #endregion #region Direct Conversions @@ -907,18 +907,17 @@ public static int GetWordCount(BigInteger self) { public static string/*!*/ __format__(CodeContext/*!*/ context, BigInteger/*!*/ self, [NotNull]string/*!*/ formatSpec) { StringFormatSpec spec = StringFormatSpec.FromString(formatSpec); - if (spec.Precision != null) { - throw PythonOps.ValueError("Precision not allowed in integer format specifier"); - } - BigInteger val = self; if (self < 0) { val = -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) { @@ -931,6 +930,9 @@ public static int GetWordCount(BigInteger self) { break; case null: case 'd': + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } if (spec.ThousandsComma) { var width = spec.Width ?? 0; // If we're inserting commas, and we're padding with leading zeros. @@ -995,22 +997,41 @@ public static int GetWordCount(BigInteger self) { } break; case 'X': + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } digits = AbsToHex(val, false); break; case 'x': + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } digits = AbsToHex(val, true); break; case 'o': // octal + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } digits = ToOctal(val, true); break; case 'b': // binary + if (spec.Precision != null) { + throw PythonOps.ValueError("Precision not allowed in integer format specifier"); + } digits = ToBinary(val, false, 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("long int too large to convert to int"); } else if(iVal < 0 || iVal > 0xFF) { throw PythonOps.OverflowError("%c arg not in range(0x10000)"); @@ -1035,7 +1056,7 @@ private static string ToOctal(BigInteger val, bool lowercase) { return ToDigits(val, 8, lowercase); } - internal static string ToBinary(BigInteger val) { + internal static string ToBinary(BigInteger val) { string res = ToBinary(val.Abs(), true, true); if (val.IsNegative()) { res = "-" + res; @@ -1047,7 +1068,7 @@ private static string ToBinary(BigInteger val, bool includeType, bool lowercase) Debug.Assert(!val.IsNegative()); string digits = ToDigits(val, 2, lowercase); - + if (includeType) { digits = (lowercase ? "0b" : "0B") + digits; } @@ -1062,7 +1083,7 @@ private static string ToBinary(BigInteger val, bool includeType, bool lowercase) StringBuilder tmp = new StringBuilder(); tmp.Append(digits[0]); - + for (int i = 1; i < maxPrecision && i < digits.Length; i++) { // append if we have a significant digit or if we are forcing a minimum precision if (digits[i] != '0' || i <= minPrecision) { @@ -1129,7 +1150,7 @@ private static string ToBinary(BigInteger val, bool includeType, bool lowercase) for (int i = str.Length - 1; i >= 0; i--) { res.Append(str[i]); } - + return res.ToString(); } } diff --git a/Tests/test_strformat.py b/Tests/test_strformat.py index ba8eff200..d271cac47 100644 --- a/Tests/test_strformat.py +++ b/Tests/test_strformat.py @@ -395,49 +395,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'), @@ -522,9 +499,19 @@ 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: self.assertEqual(value.__format__(spec), result) + # upper-case version + upper_spec = spec.translate(upper_spec_trans) + if spec != upper_spec: + actual = value.__format__(upper_spec) + result = result.upper() + self.assertEqual(actual, result, "value:{0}, spec:{1}, (expected) result:{2}, actual:{3}, expr:({0}).__format__('{1}')".format(value, upper_spec, result, actual)) + + if is_netcoreapp21: return # https://github.com/IronLanguages/ironpython3/issues/751 # check locale specific formatting @@ -1131,6 +1118,15 @@ def test_long___format__(self): (long(111234540100), 'E', '1.112345E+11'), (long(100000), 'n', '100000'), + + # 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: