Skip to content

Commit a846473

Browse files
committed
Bug-fixes to float formatting and some tests
We make some improvements to grouping and the default precision. There are no doubt many other Python 3 advances we should catch up with, but they can wait.
1 parent 406791f commit a846473

File tree

4 files changed

+474
-187
lines changed

4 files changed

+474
-187
lines changed

rt4core/src/main/java/uk/co/farowl/vsj4/core/PyFloat.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -287,29 +287,28 @@ private static String formatDouble(double value,
287287
static class Formatter extends FloatFormatter {
288288

289289
/**
290-
* If {@code true}, give {@code printf}-style meanings to
290+
* If {@code true}, give old-style %-formatting meanings to
291291
* {@link FormatSpec#type}.
292292
*/
293-
final boolean printf;
293+
final boolean oldfmt;
294294

295295
/**
296296
* Prepare a {@link Formatter} in support of
297-
* {@code str.__mod__}, that is, traditional
298-
* {@code printf}-style formatting.
297+
* {@code str.__mod__}, that is, old-style %-formatting.
299298
*
300299
* @param spec a parsed format specification.
301-
* @param printf f {@code true}, interpret {@code spec}
302-
* {@code printf}-style, otherwise as
300+
* @param oldfmt f {@code true}, interpret {@code spec}
301+
* old-style %-formatting, otherwise as
303302
* {@link Formatter#Formatter(FormatSpec)
304303
* Formatter(FormatSpec)}
305304
* @throws FormatOverflow if a value is out of range (including
306305
* the precision)
307306
* @throws FormatError if an unsupported format character is
308307
* encountered
309308
*/
310-
Formatter(FormatSpec spec, boolean printf) throws FormatError {
311-
super(validated(spec, printf));
312-
this.printf = printf;
309+
Formatter(FormatSpec spec, boolean oldfmt) throws FormatError {
310+
super(validated(spec, oldfmt));
311+
this.oldfmt = oldfmt;
313312
}
314313

315314
/**
@@ -334,14 +333,14 @@ static class Formatter extends FloatFormatter {
334333
* @throws FormatError on failure to validate
335334
*/
336335
private static FormatSpec validated(FormatSpec spec,
337-
boolean printf) throws FormatError {
338-
String type = TYPE.getName();
336+
boolean oldfmt) throws FormatError {
339337

340338
switch (spec.type) {
341339

342340
case 'n':
343-
if (spec.grouping) {
344-
throw notAllowed("Grouping", type, spec.type);
341+
if (spec.grouper != 0) {
342+
throw notAllowed("Grouping", spec.grouper,
343+
TYPE.getName(), spec.type);
345344
}
346345
//$FALL-THROUGH$
347346

@@ -353,21 +352,22 @@ private static FormatSpec validated(FormatSpec spec,
353352
case 'F':
354353
case 'G':
355354
case '%':
355+
// TODO Alternate forms allowed in Python 3
356356
// Check for disallowed parts of the specification
357357
if (spec.alternate) {
358-
throw alternateFormNotAllowed(type);
358+
throw alternateFormNotAllowed(TYPE.getName());
359359
}
360360
break;
361361

362362
case 'r':
363363
case 's':
364-
// Only allow for printf-style formatting
365-
if (printf) { break; }
364+
// Only allow for old-style %-formatting
365+
if (oldfmt) { break; }
366366
//$FALL-THROUGH$
367367

368368
default:
369369
// The type code was not recognised
370-
throw unknownFormat(spec.type, type);
370+
throw unknownFormat(spec.type, TYPE.getName());
371371
}
372372

373373
/*

rt4core/src/main/java/uk/co/farowl/vsj4/stringlib/FloatFormatter.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import uk.co.farowl.vsj4.stringlib.InternalFormat.FormatOverflow;
1111
import uk.co.farowl.vsj4.stringlib.InternalFormat.FormatSpec;
1212

13-
1413
/**
1514
* A class that provides the implementation of floating-point
1615
* formatting. In a limited way, it acts like a StringBuilder to which
@@ -188,7 +187,8 @@ public FloatFormatter format(double value)
188187
* here. At the point this is used, we know the {@link #spec} is one
189188
* of the floating-point types. This entry point allows explicit
190189
* control of the prefix of positive numbers, overriding defaults
191-
* for the format type.
190+
* for the format type. (We use this before the imaginary part of a
191+
* complex number.)
192192
*
193193
* @param value to convert
194194
* @param positivePrefix to use before positive values (e.g. "+") or
@@ -208,9 +208,9 @@ public FloatFormatter format(double value, String positivePrefix)
208208
*/
209209
setStart();
210210

211-
// Precision defaults to 6 (or 12 for none-format)
212-
int precision =
213-
spec.getPrecision(FormatSpec.specified(spec.type) ? 6 : 12);
211+
// Precision defaults to 6 (or 17 for none-format)
212+
int precision = spec
213+
.getPrecision(FormatSpec.specified(spec.type) ? 6 : 17);
214214

215215
// Guard against excessive result precision
216216
// XXX Possibly better raised before result is allocated/sized.
@@ -301,7 +301,7 @@ public FloatFormatter format(double value, String positivePrefix)
301301
if (Character.isUpperCase(spec.type)) { uppercase(); }
302302

303303
// If required to, group the whole-part digits.
304-
if (spec.grouping) { groupDigits(3, ','); }
304+
if (spec.group > 0) { groupDigits(spec.group, spec.grouper); }
305305

306306
return this;
307307
}
@@ -535,7 +535,8 @@ private void ensurePointAndTrailingZeros(int n) {
535535
* {@link BigDecimal} to provide conversion and rounding. These
536536
* variants are g-format proper, alternate g-format (available for
537537
* "%#g" formatting), n-format (as g but subsequently
538-
* "internationalised"), and none-format (type code FormatSpec.NONE).
538+
* "internationalised"), and none-format (type code
539+
* FormatSpec.NONE).
539540
* <p>
540541
* None-format is the basis of {@code float.__str__}.
541542
* <p>
@@ -611,7 +612,7 @@ private void format_g(double value, String positivePrefix,
611612
// Precision 0 behaves as 1
612613
precision = Math.max(1, precision);
613614

614-
// Use exponential notation if exponent would be bigger thatn:
615+
// Use exponential notation if exponent would be bigger than:
615616
int expThreshold = precision + expThresholdAdj;
616617

617618
if (signAndSpecialNumber(value, positivePrefix)) {

rt4core/src/main/java/uk/co/farowl/vsj4/stringlib/InternalFormat.java

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public static abstract class AbstractFormatter
6969
protected int lenSign;
7070
/**
7171
* The length of the whole part (to left of the decimal point or
72-
* exponent)
72+
* exponent).
7373
*/
7474
protected int lenWhole;
7575

@@ -461,13 +461,21 @@ protected void pad(int leftIndex, int n) {
461461
// Append the trailing space
462462
for (int i = 0; i < trailing; i++) { result.append(fill); }
463463

464-
// Check for special case
465-
if (align == '=' && fill == '0' && spec.grouping) {
464+
/*
465+
* Check for special case where we shall extend the grouping
466+
* already applied to the whole part into the left
467+
* 0-padding.
468+
*/
469+
if (align == '=' && fill == '0' && spec.group > 0) {
466470
/*
467-
* We must extend the grouping separator into the
468-
* padding
471+
* Extend the grouping separator into the padding,
472+
* EXCEPT if there was originally no whole part (occurs
473+
* for inf, nan).
469474
*/
470-
zeroPadAfterSignWithGroupingFixup(3, ',');
475+
if (lenWhole > leading) {
476+
zeroPadAfterSignWithGroupingFixup(spec.group,
477+
spec.grouper);
478+
}
471479
}
472480
}
473481

@@ -505,7 +513,7 @@ protected void pad(int leftIndex, int n) {
505513
* '-<b>0</b>,000,000,001,200,000,000.0000'
506514
* </pre>
507515
*
508-
* @param groupSize normally 3.
516+
* @param groupSize normally 3 or 4.
509517
* @param comma or some other character to use as a separator.
510518
*/
511519
protected void zeroPadAfterSignWithGroupingFixup(int groupSize,
@@ -787,11 +795,10 @@ public static class FormatSpec {
787795
public final boolean alternate;
788796
/** Width to which to pad the result, or -1 if unspecified. */
789797
public final int width;
790-
/**
791-
* Insert the grouping separator (which in Python always
792-
* indicates a group-size of 3).
793-
*/
794-
public final boolean grouping;
798+
/** Grouping size (or &le;0 for no grouping)). */
799+
public final int group;
800+
/** The group separator (if {@link #group} is &gt;0). */
801+
public final char grouper;
795802
/** Precision decoded from the format, or -1 if unspecified. */
796803
public final int precision;
797804
/** Type key from the format, or U+FFFF if unspecified. */
@@ -827,7 +834,7 @@ public static final boolean specified(char c) {
827834
* has been specified).
828835
*/
829836
public static final boolean specified(int value) {
830-
return value >= 0;
837+
return value >= 0; // UNSPECIFIED = -1
831838
}
832839

833840
/**
@@ -845,19 +852,21 @@ public static final boolean specified(int value) {
845852
* @param alternate true to request alternate formatting mode
846853
* ({@code '#'} flag).
847854
* @param width of field after padding or -1 to default
848-
* @param grouping true to request comma-separated groups
855+
* @param group if non-zero digit group size (usually 3 or 4)
856+
* @param grouper character separating groups
849857
* @param precision (e.g. decimal places) or -1 to default
850858
* @param type indicator character
851859
*/
852860
public FormatSpec(char fill, char align, char sign,
853-
boolean alternate, int width, boolean grouping,
861+
boolean alternate, int width, int group, char grouper,
854862
int precision, char type) {
855863
this.fill = fill;
856864
this.align = align;
857865
this.sign = sign;
858866
this.alternate = alternate;
859867
this.width = width;
860-
this.grouping = grouping;
868+
this.group = group;
869+
this.grouper = grouper;
861870
this.precision = precision;
862871
this.type = type;
863872
}
@@ -874,7 +883,7 @@ public String toString() {
874883
if (specified(sign)) { buf.append(sign); }
875884
if (alternate) { buf.append('#'); }
876885
if (specified(width)) { buf.append(width); }
877-
if (grouping) { buf.append(','); }
886+
if (specified(group)) { buf.append(grouper); }
878887
if (specified(precision)) {
879888
buf.append('.').append(precision);
880889
}
@@ -908,7 +917,8 @@ public FormatSpec withDefaults(FormatSpec other) {
908917
specified(sign) ? sign : other.sign, //
909918
alternate || other.alternate, //
910919
specified(width) ? width : other.width, //
911-
grouping || other.grouping, //
920+
specified(group) ? group : other.group, //
921+
specified(grouper) ? grouper : other.grouper, //
912922
specified(precision) ? precision : other.precision, //
913923
specified(type) ? type : other.type //
914924
);
@@ -918,16 +928,16 @@ public FormatSpec withDefaults(FormatSpec other) {
918928
* Defaults applicable to most numeric types. Equivalent to "
919929
* &gt;"
920930
*/
921-
public static final FormatSpec NUMERIC = new FormatSpec(' ',
922-
'>', FormatSpec.NONE, false, FormatSpec.UNSPECIFIED,
923-
false, FormatSpec.UNSPECIFIED, FormatSpec.NONE);
931+
public static final FormatSpec NUMERIC =
932+
new FormatSpec(' ', '>', NONE, false, UNSPECIFIED,
933+
UNSPECIFIED, NONE, UNSPECIFIED, NONE);
924934

925935
/**
926936
* Defaults applicable to string types. Equivalent to " &lt;"
927937
*/
928-
public static final FormatSpec STRING = new FormatSpec(' ', '<',
929-
FormatSpec.NONE, false, FormatSpec.UNSPECIFIED, false,
930-
FormatSpec.UNSPECIFIED, FormatSpec.NONE);
938+
public static final FormatSpec STRING =
939+
new FormatSpec(' ', '<', NONE, false, UNSPECIFIED,
940+
UNSPECIFIED, NONE, UNSPECIFIED, NONE);
931941

932942
/**
933943
* Constructor offering just precision and type.
@@ -940,7 +950,7 @@ public FormatSpec withDefaults(FormatSpec other) {
940950
* @param type indicator character
941951
*/
942952
public FormatSpec(int precision, char type) {
943-
this(' ', '>', FormatSpec.NONE, false, UNSPECIFIED, false,
953+
this(' ', '>', NONE, false, UNSPECIFIED, UNSPECIFIED, NONE,
944954
precision, type);
945955
}
946956

@@ -1009,6 +1019,11 @@ private static class Parser {
10091019
this.ptr = 0;
10101020
}
10111021

1022+
@Override
1023+
public String toString() {
1024+
return spec.substring(0, ptr) + "|" + spec.substring(ptr);
1025+
}
1026+
10121027
/**
10131028
* Parse the specification with which this object was
10141029
* initialised into an {@link FormatSpec}, which is an object
@@ -1029,11 +1044,13 @@ private static class Parser {
10291044
*/
10301045
FormatSpec parse() {
10311046

1032-
char fill = FormatSpec.NONE, align = FormatSpec.NONE;
1033-
char sign = FormatSpec.NONE, type = FormatSpec.NONE;
1034-
boolean alternate = false, grouping = false;
1035-
int width = FormatSpec.UNSPECIFIED,
1036-
precision = FormatSpec.UNSPECIFIED;
1047+
char fill = FormatSpec.NONE, align = FormatSpec.NONE,
1048+
sign = FormatSpec.NONE, type = FormatSpec.NONE,
1049+
grouper = FormatSpec.NONE;
1050+
boolean alternate = false;
1051+
int width = FormatSpec.UNSPECIFIED;
1052+
int precision = FormatSpec.UNSPECIFIED;
1053+
int group = FormatSpec.UNSPECIFIED;
10371054

10381055
// Scan [[fill]align] ...
10391056
if (isAlign()) {
@@ -1082,8 +1099,8 @@ FormatSpec parse() {
10821099
// Scan [width]
10831100
if (isDigit()) { width = scanInteger(); }
10841101

1085-
// Scan [,][.precision][type]
1086-
grouping = scanPast(',');
1102+
// Scan [grouper][.precision][type]
1103+
if (isAt(",_")) { grouper = spec.charAt(ptr++); }
10871104

10881105
// Scan [.precision]
10891106
if (scanPast('.')) {
@@ -1098,6 +1115,12 @@ FormatSpec parse() {
10981115
// Scan [type]
10991116
if (ptr < spec.length()) { type = spec.charAt(ptr++); }
11001117

1118+
// If grouping was specified, work out the group size.
1119+
if (grouper != FormatSpec.NONE) {
1120+
// Hex, octal and binary group in 4s. Otherwise 3s.
1121+
group = "boxX".indexOf(type) < 0 ? 3 : 4;
1122+
}
1123+
11011124
// If we haven't reached the end, something is wrong
11021125
if (ptr != spec.length()) {
11031126
throw new IllegalArgumentException(
@@ -1106,7 +1129,7 @@ FormatSpec parse() {
11061129

11071130
// Create a specification
11081131
return new FormatSpec(fill, align, sign, alternate, width,
1109-
grouping, precision, type);
1132+
group, grouper, precision, type);
11101133
}
11111134

11121135
/**
@@ -1152,7 +1175,6 @@ private int scanInteger() {
11521175
while (isDigit()) { ptr++; }
11531176
return Integer.parseInt(spec.substring(p, ptr));
11541177
}
1155-
11561178
}
11571179

11581180
/**

0 commit comments

Comments
 (0)