Skip to content

Commit

Permalink
Fix formatting of int as float with precision
Browse files Browse the repository at this point in the history
  • Loading branch information
slozier committed Jan 2, 2023
1 parent 84ea2f3 commit 98caf16
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 54 deletions.
30 changes: 24 additions & 6 deletions Src/IronPython/Runtime/Operations/BigIntegerOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;

Expand All @@ -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)");
Expand Down
26 changes: 21 additions & 5 deletions Src/IronPython/Runtime/Operations/IntOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;

Expand All @@ -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)");
}
Expand Down
79 changes: 36 additions & 43 deletions Tests/test_strformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'),

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit 98caf16

Please sign in to comment.