Skip to content

Commit 30463ab

Browse files
authored
Formatting cleanup (#1499)
* Formatting cleanup * Cleanup * Fix invalid formats of zero padded negative values and add tests * Fix header
1 parent 6cf851b commit 30463ab

File tree

8 files changed

+158
-491
lines changed

8 files changed

+158
-491
lines changed

Src/IronPython/Runtime/FormattingHelper.cs

Lines changed: 18 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,46 @@
1-
/* ****************************************************************************
2-
*
3-
* Copyright (c) Microsoft Corporation.
4-
*
5-
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
6-
* copy of the license can be found in the License.html file at the root of this distribution. If
7-
* you cannot locate the Apache License, Version 2.0, please send an email to
8-
* dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
9-
* by the terms of the Apache License, Version 2.0.
10-
*
11-
* You must not remove this notice, or any other, from this software.
12-
*
13-
*
14-
* ***************************************************************************/
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
154

165
using System;
17-
using System.Collections.Generic;
186
using System.Globalization;
19-
using System.Linq;
207
using System.Text;
218
using System.Threading;
229

23-
namespace IronPython.Runtime
24-
{
25-
internal static class FormattingHelper
26-
{
27-
private static NumberFormatInfo _invariantCommaSeperatorInfo;
10+
namespace IronPython.Runtime {
11+
internal static class FormattingHelper {
12+
private static NumberFormatInfo _invariantUnderscoreSeperatorInfo;
2813

2914
/// <summary>
3015
/// Helper NumberFormatInfo for use by int/BigInteger __format__ routines
31-
/// for width specified leading zero support that contains ','s every 3 digits.
16+
/// for width specified leading zero support that contains '_'s every 3 digits.
3217
/// i.e. For use by d/g/G format specifiers. NOT for use by n format specifiers.
3318
/// </summary>
34-
public static NumberFormatInfo InvariantCommaNumberInfo {
19+
public static NumberFormatInfo InvariantUnderscoreNumberInfo {
3520
get {
36-
if (_invariantCommaSeperatorInfo == null) {
21+
if (_invariantUnderscoreSeperatorInfo == null) {
3722
Interlocked.CompareExchange(
38-
ref _invariantCommaSeperatorInfo,
39-
new NumberFormatInfo()
40-
{
41-
NumberGroupSeparator = ",",
23+
ref _invariantUnderscoreSeperatorInfo,
24+
new NumberFormatInfo() {
25+
NumberGroupSeparator = "_",
4226
NumberDecimalSeparator = ".",
43-
NumberGroupSizes = new int[] {3}
27+
NumberGroupSizes = new int[] { 3 }
4428
},
4529
null
4630
);
4731
}
48-
return _invariantCommaSeperatorInfo;
32+
return _invariantUnderscoreSeperatorInfo;
4933
}
5034
}
5135

52-
public static string/*!*/ ToCultureString<T>(T/*!*/ val, NumberFormatInfo/*!*/ nfi, StringFormatSpec spec) {
36+
public static string/*!*/ ToCultureString<T>(T/*!*/ val, NumberFormatInfo/*!*/ nfi, StringFormatSpec spec, int? overrideWidth = null) {
5337
string separator = nfi.NumberGroupSeparator;
5438
int[] separatorLocations = nfi.NumberGroupSizes;
5539
string digits = val.ToString();
5640

5741
// If we're adding leading zeros, we need to know how
5842
// many we need.
59-
int width = spec.Width ?? 0;
43+
int width = overrideWidth ?? spec.Width ?? 0;
6044
int fillerLength = Math.Max(width - digits.Length, 0);
6145
bool addLeadingZeros = (spec.Fill ?? '\0') == '0' && width > digits.Length;
6246
int beginningOfDigits = fillerLength;
@@ -127,12 +111,10 @@ public static NumberFormatInfo InvariantCommaNumberInfo {
127111
break;
128112
}
129113
}
130-
}
131-
else {
114+
} else {
132115
res.Remove(0, beginningOfMaximumWidth);
133116
}
134-
}
135-
else {
117+
} else {
136118
// If we ran out of remainingWidth just formatting
137119
// the actual digits, then remove any extra leading zeros
138120
// we added.

Src/IronPython/Runtime/NewStringFormatter.cs

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,11 @@
1-
/* ****************************************************************************
2-
*
3-
* Copyright (c) Microsoft Corporation.
4-
*
5-
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
6-
* copy of the license can be found in the License.html file at the root of this distribution. If
7-
* you cannot locate the Apache License, Version 2.0, please send an email to
8-
* dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
9-
* by the terms of the Apache License, Version 2.0.
10-
*
11-
* You must not remove this notice, or any other, from this software.
12-
*
13-
*
14-
* ***************************************************************************/
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
154

165
using System;
176
using System.Collections.Generic;
187
using System.Text;
19-
using System.Diagnostics;
208

21-
using Microsoft.Scripting;
229
using Microsoft.Scripting.Runtime;
2310
using Microsoft.Scripting.Utils;
2411

Src/IronPython/Runtime/Operations/BigIntegerOps.cs

Lines changed: 22 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -742,10 +742,8 @@ public static BigInteger ToBigInteger(BigInteger self) {
742742
throw PythonOps.ValueError("Precision not allowed in integer format specifier");
743743
}
744744

745-
BigInteger val = self;
746-
if (self < 0) {
747-
val = -self;
748-
}
745+
BigInteger val = self.Abs();
746+
749747
string digits;
750748

751749
switch (spec.Type) {
@@ -758,84 +756,50 @@ public static BigInteger ToBigInteger(BigInteger self) {
758756
goto case 'd';
759757
}
760758

761-
digits = FormattingHelper.ToCultureString(val, context.LanguageContext.NumericCulture.NumberFormat, spec);
759+
if (spec.Fill == '0' && spec.Width > 1) {
760+
digits = FormattingHelper.ToCultureString(val, culture.NumberFormat, spec, (spec.Sign != null && spec.Sign != '-' || self < 0) ? spec.Width - 1 : null);
761+
}
762+
else {
763+
digits = FormattingHelper.ToCultureString(val, culture.NumberFormat, spec);
764+
}
762765
break;
763766
case null:
764767
case 'd':
765-
if (spec.ThousandsComma) {
766-
var width = spec.Width ?? 0;
768+
if (spec.ThousandsComma || spec.ThousandsUnderscore) {
769+
var numberFormat = spec.ThousandsUnderscore ? FormattingHelper.InvariantUnderscoreNumberInfo : CultureInfo.InvariantCulture.NumberFormat;
770+
767771
// If we're inserting commas, and we're padding with leading zeros.
768772
// AlignNumericText won't know where to place the commas,
769-
// so force .Net to help us out here.
770-
if (spec.Fill.HasValue && spec.Fill.Value == '0' && width > 1) {
771-
digits = val.ToString(FormattingHelper.ToCultureString(self, FormattingHelper.InvariantCommaNumberInfo, spec));
773+
// so use FormattingHelper.ToCultureString for that support.
774+
if (spec.Fill == '0' && spec.Width > 1) {
775+
digits = FormattingHelper.ToCultureString(val, numberFormat, spec, (spec.Sign != null && spec.Sign != '-' || self < 0) ? spec.Width - 1 : null);
776+
} else {
777+
digits = val.ToString("#,0", numberFormat);
772778
}
773-
else {
774-
digits = val.ToString("#,0", CultureInfo.InvariantCulture);
775-
}
776-
}
777-
else {
779+
} else {
778780
digits = val.ToString("D", CultureInfo.InvariantCulture);
779781
}
780782
break;
781783
case '%':
782-
if (spec.ThousandsComma) {
783-
digits = val.ToString("#,0.000000%", CultureInfo.InvariantCulture);
784-
} else {
785-
digits = val.ToString("0.000000%", CultureInfo.InvariantCulture);
786-
}
787-
break;
788784
case 'e':
789-
if (spec.ThousandsComma) {
790-
digits = val.ToString("#,0.000000e+00", CultureInfo.InvariantCulture);
791-
} else {
792-
digits = val.ToString("0.000000e+00", CultureInfo.InvariantCulture);
793-
}
794-
break;
795785
case 'E':
796-
if (spec.ThousandsComma) {
797-
digits = val.ToString("#,0.000000E+00", CultureInfo.InvariantCulture);
798-
} else {
799-
digits = val.ToString("0.000000E+00", CultureInfo.InvariantCulture);
800-
}
801-
break;
802786
case 'f':
803787
case 'F':
804-
if (spec.ThousandsComma) {
805-
digits = val.ToString("#,########0.000000", CultureInfo.InvariantCulture);
806-
} else {
807-
digits = val.ToString("#########0.000000", CultureInfo.InvariantCulture);
808-
}
809-
break;
810788
case 'g':
811-
if (val >= 1000000) {
812-
digits = val.ToString("0.#####e+00", CultureInfo.InvariantCulture);
813-
} else if (spec.ThousandsComma) {
814-
goto case 'd';
815-
} else {
816-
digits = val.ToString(CultureInfo.InvariantCulture);
817-
}
818-
break;
819789
case 'G':
820-
if (val >= 1000000) {
821-
digits = val.ToString("0.#####E+00", CultureInfo.InvariantCulture);
822-
} else if (spec.ThousandsComma) {
823-
goto case 'd';
824-
} else {
825-
digits = val.ToString(CultureInfo.InvariantCulture);
826-
}
790+
digits = DoubleOps.DoubleToFormatString(context, ToDouble(val), spec);
827791
break;
828792
case 'X':
829-
digits = AbsToHex(val, false);
793+
digits = AbsToHex(val, lowercase: false);
830794
break;
831795
case 'x':
832-
digits = AbsToHex(val, true);
796+
digits = AbsToHex(val, lowercase: true);
833797
break;
834798
case 'o': // octal
835-
digits = ToOctal(val, true);
799+
digits = ToOctal(val, lowercase: true);
836800
break;
837801
case 'b': // binary
838-
digits = ToBinary(val, false, true);
802+
digits = ToBinary(val, includeType: false, lowercase: true);
839803
break;
840804
case 'c': // single char
841805
int iVal;

Src/IronPython/Runtime/Operations/FloatOps.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -813,7 +813,7 @@ public static string __format__(CodeContext/*!*/ context, double self, [NotNone]
813813
/// <summary>
814814
/// Returns the digits for the format spec, no sign is included.
815815
/// </summary>
816-
private static string DoubleToFormatString(CodeContext/*!*/ context, double self, StringFormatSpec/*!*/ spec) {
816+
internal static string DoubleToFormatString(CodeContext/*!*/ context, double self, StringFormatSpec/*!*/ spec) {
817817
self = Math.Abs(self);
818818
const int DefaultPrecision = 6;
819819
int precision = spec.Precision ?? DefaultPrecision;

Src/IronPython/Runtime/Operations/IntOps.cs

Lines changed: 20 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
#nullable enable
66

77
using System;
8-
using System.Collections.Generic;
8+
using System.Diagnostics;
99
using System.Globalization;
10-
using System.Linq;
1110
using System.Numerics;
1211
using System.Text;
1312

@@ -205,14 +204,17 @@ public static BigInteger ToBigInteger(this int self) {
205204
#region Public API - String/Bytes
206205

207206
public static string __format__(CodeContext/*!*/ context, int self, [NotNone] string/*!*/ formatSpec) {
207+
if (self == int.MinValue) return BigIntegerOps.__format__(context, self, formatSpec);
208+
208209
StringFormatSpec spec = StringFormatSpec.FromString(formatSpec);
209210

210211
if (spec.Precision != null) {
211212
throw PythonOps.ValueError("Precision not allowed in integer format specifier");
212213
}
213214

215+
int val = Math.Abs(self);
216+
214217
string digits;
215-
int width = 0;
216218

217219
switch (spec.Type) {
218220
case 'n':
@@ -223,94 +225,53 @@ public static string __format__(CodeContext/*!*/ context, int self, [NotNone] st
223225
// include any formatting info.
224226
goto case 'd';
225227
}
226-
width = spec.Width ?? 0;
227228

228229
// If we're padding with leading zeros and we might be inserting
229230
// culture sensitive number group separators. (i.e. commas)
230231
// So use FormattingHelper.ToCultureString for that support.
231-
if (spec.Fill.HasValue && spec.Fill.Value == '0' && width > 1) {
232-
digits = FormattingHelper.ToCultureString(self, culture.NumberFormat, spec);
232+
if (spec.Fill == '0' && spec.Width > 1) {
233+
digits = FormattingHelper.ToCultureString(val, culture.NumberFormat, spec, (spec.Sign != null && spec.Sign != '-' || self < 0) ? spec.Width - 1 : null);
233234
} else {
234-
digits = self.ToString("N0", culture);
235+
digits = val.ToString("N0", culture);
235236
}
236237
break;
237238
case null:
238239
case 'd':
239-
if (spec.ThousandsComma) {
240-
width = spec.Width ?? 0;
240+
if (spec.ThousandsComma || spec.ThousandsUnderscore) {
241+
var numberFormat = spec.ThousandsUnderscore ? FormattingHelper.InvariantUnderscoreNumberInfo : CultureInfo.InvariantCulture.NumberFormat;
241242

242243
// If we're inserting commas, and we're padding with leading zeros.
243244
// AlignNumericText won't know where to place the commas,
244245
// so use FormattingHelper.ToCultureString for that support.
245-
if (spec.Fill.HasValue && spec.Fill.Value == '0' && width > 1) {
246-
digits = FormattingHelper.ToCultureString(self, FormattingHelper.InvariantCommaNumberInfo, spec);
246+
if (spec.Fill == '0' && spec.Width > 1) {
247+
digits = FormattingHelper.ToCultureString(val, numberFormat, spec, (spec.Sign != null && spec.Sign != '-' || self < 0) ? spec.Width - 1 : null);
247248
} else {
248-
digits = self.ToString("#,0", CultureInfo.InvariantCulture);
249+
digits = val.ToString("#,0", numberFormat);
249250
}
250251
} else {
251-
digits = self.ToString("D", CultureInfo.InvariantCulture);
252+
digits = val.ToString("D", CultureInfo.InvariantCulture);
252253
}
253254
break;
254255
case '%':
255-
if (spec.ThousandsComma) {
256-
digits = self.ToString("#,0.000000%", CultureInfo.InvariantCulture);
257-
} else {
258-
digits = self.ToString("0.000000%", CultureInfo.InvariantCulture);
259-
}
260-
break;
261256
case 'e':
262-
if (spec.ThousandsComma) {
263-
digits = self.ToString("#,0.000000e+00", CultureInfo.InvariantCulture);
264-
} else {
265-
digits = self.ToString("0.000000e+00", CultureInfo.InvariantCulture);
266-
}
267-
break;
268257
case 'E':
269-
if (spec.ThousandsComma) {
270-
digits = self.ToString("#,0.000000E+00", CultureInfo.InvariantCulture);
271-
} else {
272-
digits = self.ToString("0.000000E+00", CultureInfo.InvariantCulture);
273-
}
274-
break;
275258
case 'f':
276259
case 'F':
277-
if (spec.ThousandsComma) {
278-
digits = self.ToString("#,########0.000000", CultureInfo.InvariantCulture);
279-
} else {
280-
digits = self.ToString("#########0.000000", CultureInfo.InvariantCulture);
281-
}
282-
break;
283260
case 'g':
284-
if (self >= 1000000 || self <= -1000000) {
285-
digits = self.ToString("0.#####e+00", CultureInfo.InvariantCulture);
286-
} else if (spec.ThousandsComma) {
287-
// Handle the common case in 'd'.
288-
goto case 'd';
289-
} else {
290-
digits = self.ToString(CultureInfo.InvariantCulture);
291-
}
292-
break;
293261
case 'G':
294-
if (self >= 1000000 || self <= -1000000) {
295-
digits = self.ToString("0.#####E+00", CultureInfo.InvariantCulture);
296-
} else if (spec.ThousandsComma) {
297-
// Handle the common case in 'd'.
298-
goto case 'd';
299-
} else {
300-
digits = self.ToString(CultureInfo.InvariantCulture);
301-
}
262+
digits = DoubleOps.DoubleToFormatString(context, val, spec);
302263
break;
303264
case 'X':
304-
digits = ToHex(self, false);
265+
digits = ToHex(val, lowercase: false);
305266
break;
306267
case 'x':
307-
digits = ToHex(self, true);
268+
digits = ToHex(val, lowercase: true);
308269
break;
309270
case 'o': // octal
310-
digits = ToOctal(self, true);
271+
digits = ToOctal(val, lowercase: true);
311272
break;
312273
case 'b': // binary
313-
digits = ToBinary(self, false);
274+
digits = ToBinary(val, includeType: false);
314275
break;
315276
case 'c': // single char
316277
if (spec.Sign != null) {
@@ -327,9 +288,7 @@ public static string __format__(CodeContext/*!*/ context, int self, [NotNone] st
327288
throw PythonOps.ValueError("Unknown format code '{0}' for object of type 'int'", spec.TypeRepr);
328289
}
329290

330-
if (self < 0 && digits[0] == '-') {
331-
digits = digits.Substring(1);
332-
}
291+
Debug.Assert(digits[0] != '-');
333292

334293
return spec.AlignNumericText(digits, self == 0, self > 0);
335294
}

0 commit comments

Comments
 (0)