1 package io.jawk.jrt;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import java.math.BigDecimal;
26 import java.math.BigInteger;
27 import java.math.MathContext;
28 import java.math.RoundingMode;
29 import java.text.DecimalFormatSymbols;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.IllegalFormatException;
33 import java.util.List;
34 import java.util.Locale;
35 import java.util.concurrent.ConcurrentHashMap;
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71 public final class AwkPrintf {
72
73
74 public static final String DEFAULT_CONVFMT = "%.6g";
75
76
77 private static final String CONVERSION_CHARS = "diouxXeEfFgGaAcs";
78
79
80 private static final String LENGTH_MODIFIERS = "hjlLtz";
81
82
83 private static final String NUL_STRING = Character.toString((char) 0);
84
85
86 private static final double TWO_POW_63 = 9.223372036854775808e18;
87
88
89 private static final BigInteger TWO_POW_64 = BigInteger.ONE.shiftLeft(64);
90
91
92 private static final int OPERAND_NONE = 0;
93
94
95 private static final int OPERAND_FIXED = 1;
96
97
98 private static final int OPERAND_STAR = 2;
99
100
101 private static final int OPERAND_STAR_POSITIONAL = 3;
102
103
104 private static final Flags NO_FLAGS = new Flags(false, false, false, false, false, false);
105
106
107 private static final int FORMAT_CACHE_LIMIT = 256;
108
109
110
111
112
113
114 private static final ConcurrentHashMap<String, Segment[]> FORMAT_CACHE = new ConcurrentHashMap<String, Segment[]>();
115
116
117 private static final int LOCALE_SYMBOLS_CACHE_LIMIT = 64;
118
119
120
121
122
123
124 private static final ConcurrentHashMap<Locale, LocaleSymbols> LOCALE_SYMBOLS_CACHE = new ConcurrentHashMap<Locale, LocaleSymbols>();
125
126 private AwkPrintf() {
127 throw new UnsupportedOperationException();
128 }
129
130
131
132
133
134
135
136
137
138
139
140 public static String sprintf(final String format, final Object... args) {
141 return sprintf(Locale.US, DEFAULT_CONVFMT, format, args);
142 }
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157 public static String sprintf(final Locale locale, final String convfmt, final String format, final Object... args) {
158 Locale actualLocale = locale == null ? Locale.US : locale;
159
160
161 String actualConvfmt = convfmt == null ? DEFAULT_CONVFMT : convfmt;
162 Object[] actualArgs = args == null ? new Object[0] : args;
163 return new AwkPrintfFormatter(actualLocale, actualConvfmt, format, actualArgs).format();
164 }
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182 public static String toAwkString(final Object value, final String conversionFormat, final Locale locale) {
183 if (value == null) {
184 return "";
185 }
186 if (value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte) {
187
188 return Long.toString(((Number) value).longValue());
189 }
190 if (!(value instanceof Number)) {
191 return value.toString();
192 }
193 return numberToAwkString(((Number) value).doubleValue(), conversionFormat, locale);
194 }
195
196 private static String numberToAwkString(final double number, final String conversionFormat, final Locale locale) {
197 if (Double.isNaN(number)) {
198 return "nan";
199 }
200 if (Double.isInfinite(number)) {
201 return number > 0 ? "inf" : "-inf";
202 }
203 if (JRT.isActuallyLong(number)) {
204 double rounded = Math.rint(number);
205 if (rounded >= -TWO_POW_63 && rounded < TWO_POW_63) {
206 return Long.toString((long) rounded);
207 }
208
209 return new BigDecimal(rounded).toBigInteger().toString();
210 }
211
212
213 String fmt = conversionFormat == null ? DEFAULT_CONVFMT : conversionFormat;
214 return sprintf(locale, DEFAULT_CONVFMT, fmt, Double.valueOf(number));
215 }
216
217
218
219
220 private static final class Flags {
221
222 private final boolean leftJustify;
223 private final boolean plusSign;
224 private final boolean spaceSign;
225 private final boolean zeroPad;
226 private final boolean alternate;
227 private final boolean grouping;
228
229 Flags(boolean leftJustify, boolean plusSign, boolean spaceSign, boolean zeroPad, boolean alternate,
230 boolean grouping) {
231 this.leftJustify = leftJustify;
232 this.plusSign = plusSign;
233 this.spaceSign = spaceSign;
234 this.zeroPad = zeroPad;
235 this.alternate = alternate;
236 this.grouping = grouping;
237 }
238
239
240
241
242
243
244
245 Flags withoutGrouping() {
246 return grouping ? new Flags(leftJustify, plusSign, spaceSign, zeroPad, alternate, false) : this;
247 }
248
249
250
251
252
253
254
255 Flags withLeftJustify() {
256 return leftJustify ? this : new Flags(true, plusSign, spaceSign, zeroPad, alternate, grouping);
257 }
258 }
259
260
261
262
263 private static final class LocaleSymbols {
264
265 private final char decimalSeparator;
266 private final char groupingSeparator;
267
268 LocaleSymbols(char decimalSeparator, char groupingSeparator) {
269 this.decimalSeparator = decimalSeparator;
270 this.groupingSeparator = groupingSeparator;
271 }
272 }
273
274
275
276
277
278
279
280
281 private static final class Segment {
282
283
284 private final String literal;
285
286
287 private final int argPosition;
288
289 private final Flags flags;
290
291
292 private final int widthKind;
293
294
295 private final int width;
296
297
298 private final int precisionKind;
299
300
301 private final int precision;
302
303
304 private final char conversion;
305
306
307
308
309
310 private final String parseError;
311
312 Segment(String literal, int argPosition, Flags flags, int widthKind, int width, int precisionKind,
313 int precision, char conversion, String parseError) {
314 this.literal = literal;
315 this.argPosition = argPosition;
316 this.flags = flags;
317 this.widthKind = widthKind;
318 this.width = width;
319 this.precisionKind = precisionKind;
320 this.precision = precision;
321 this.conversion = conversion;
322 this.parseError = parseError;
323 }
324 }
325
326
327
328
329
330 private static LocaleSymbols localeSymbolsFor(Locale locale) {
331 LocaleSymbols symbols = LOCALE_SYMBOLS_CACHE.get(locale);
332 if (symbols == null) {
333 DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(locale);
334 symbols = new LocaleSymbols(dfs.getDecimalSeparator(), dfs.getGroupingSeparator());
335 if (LOCALE_SYMBOLS_CACHE.size() >= LOCALE_SYMBOLS_CACHE_LIMIT) {
336 LOCALE_SYMBOLS_CACHE.clear();
337 }
338 LOCALE_SYMBOLS_CACHE.putIfAbsent(locale, symbols);
339 }
340 return symbols;
341 }
342
343
344
345
346
347 private static Segment[] compiledFormat(String format) {
348 Segment[] segments = FORMAT_CACHE.get(format);
349 if (segments == null) {
350 segments = compileFormat(format);
351 if (FORMAT_CACHE.size() >= FORMAT_CACHE_LIMIT) {
352
353
354
355 FORMAT_CACHE.clear();
356 }
357 Segment[] winner = FORMAT_CACHE.putIfAbsent(format, segments);
358 if (winner != null) {
359 return winner;
360 }
361 }
362 return segments;
363 }
364
365
366
367
368
369
370
371 private static Segment[] compileFormat(String format) {
372 List<Segment> segments = new ArrayList<Segment>();
373 StringBuilder literal = new StringBuilder();
374 int length = format.length();
375 int i = 0;
376 while (i < length) {
377 char c = format.charAt(i);
378 if (c != '%') {
379 literal.append(c);
380 i++;
381 continue;
382 }
383 if (i + 1 >= length) {
384
385 literal.append('%');
386 break;
387 }
388 if (format.charAt(i + 1) == '%') {
389 literal.append('%');
390 i += 2;
391 continue;
392 }
393 i = compileSpecifier(format, i, segments, literal);
394 if (i < 0) {
395
396
397 break;
398 }
399 }
400 flushLiteral(segments, literal);
401 return segments.toArray(new Segment[0]);
402 }
403
404
405
406
407
408
409
410 private static int compileSpecifier(String format, int start, List<Segment> segments, StringBuilder literal) {
411 int length = format.length();
412 int i = start + 1;
413
414
415 int argPosition = 0;
416 int digitsEnd = i;
417 while (digitsEnd < length && isAsciiDigit(format.charAt(digitsEnd))) {
418 digitsEnd++;
419 }
420 if (digitsEnd > i && digitsEnd < length && format.charAt(digitsEnd) == '$') {
421 argPosition = parseInt(format, i, digitsEnd);
422 if (argPosition <= 0) {
423 emitError(
424 segments,
425 literal,
426 OPERAND_NONE,
427 0,
428 "argument index with `$' must be > 0 in `" + format + "'");
429 return -1;
430 }
431 i = digitsEnd + 1;
432 }
433
434
435 boolean leftJustify = false;
436 boolean plusSign = false;
437 boolean spaceSign = false;
438 boolean zeroPad = false;
439 boolean alternate = false;
440 boolean grouping = false;
441 flagLoop: while (i < length) {
442 switch (format.charAt(i)) {
443 case '-':
444 leftJustify = true;
445 break;
446 case '+':
447 plusSign = true;
448 break;
449 case ' ':
450 spaceSign = true;
451 break;
452 case '0':
453 zeroPad = true;
454 break;
455 case '#':
456 alternate = true;
457 break;
458 case '\'':
459 grouping = true;
460 break;
461 default:
462 break flagLoop;
463 }
464 i++;
465 }
466
467
468 int widthKind = OPERAND_NONE;
469 int width = -1;
470 if (i < length && format.charAt(i) == '*') {
471 i++;
472 int starArgEnd = starPositionEnd(format, i);
473 if (starArgEnd == i && i < length && isAsciiDigit(format.charAt(i))) {
474
475
476 emitError(
477 segments,
478 literal,
479 OPERAND_NONE,
480 0,
481 "no `$' supplied for positional field width or precision in `" + format + "'");
482 return -1;
483 }
484 if (starArgEnd > i) {
485 widthKind = OPERAND_STAR_POSITIONAL;
486 width = parseInt(format, i, starArgEnd - 1);
487 i = starArgEnd;
488 } else {
489 widthKind = OPERAND_STAR;
490 }
491 } else {
492 int widthEnd = i;
493 while (widthEnd < length && isAsciiDigit(format.charAt(widthEnd))) {
494 widthEnd++;
495 }
496 if (widthEnd > i) {
497 widthKind = OPERAND_FIXED;
498 width = parseInt(format, i, widthEnd);
499 i = widthEnd;
500 }
501 }
502
503
504 int precisionKind = OPERAND_NONE;
505 int precision = -1;
506 if (i < length && format.charAt(i) == '.') {
507 i++;
508 if (i < length && format.charAt(i) == '*') {
509 i++;
510 int starArgEnd = starPositionEnd(format, i);
511 if (starArgEnd == i && i < length && isAsciiDigit(format.charAt(i))) {
512
513
514 emitError(
515 segments,
516 literal,
517 widthKind,
518 width,
519 "no `$' supplied for positional field width or precision in `" + format + "'");
520 return -1;
521 }
522 if (starArgEnd > i) {
523 precisionKind = OPERAND_STAR_POSITIONAL;
524 precision = parseInt(format, i, starArgEnd - 1);
525 i = starArgEnd;
526 } else {
527 precisionKind = OPERAND_STAR;
528 }
529 } else {
530 int precisionEnd = i;
531 while (precisionEnd < length && isAsciiDigit(format.charAt(precisionEnd))) {
532 precisionEnd++;
533 }
534 precisionKind = OPERAND_FIXED;
535 precision = precisionEnd == i ? 0 : parseInt(format, i, precisionEnd);
536 i = precisionEnd;
537 }
538 }
539
540
541
542
543 int modifierMask = 0;
544 while (i < length) {
545 int modifierIndex = LENGTH_MODIFIERS.indexOf(format.charAt(i));
546 if (modifierIndex < 0 || (modifierMask & 1 << modifierIndex) != 0) {
547 break;
548 }
549 modifierMask |= 1 << modifierIndex;
550 i++;
551 }
552
553 if (i < length && format.charAt(i) == '%') {
554
555
556
557
558
559 emitVerbatim(
560 segments,
561 literal,
562 new Segment(
563 "%",
564 argPosition,
565 NO_FLAGS,
566 widthKind,
567 width,
568 precisionKind,
569 precision,
570 (char) 0,
571 null));
572 return i + 1;
573 }
574
575 if (i >= length || CONVERSION_CHARS.indexOf(format.charAt(i)) < 0) {
576
577
578
579
580
581 int end = i < length ? i + 1 : length;
582 emitVerbatim(
583 segments,
584 literal,
585 new Segment(
586 format.substring(start, end),
587 argPosition,
588 NO_FLAGS,
589 widthKind,
590 width,
591 precisionKind,
592 precision,
593 (char) 0,
594 null));
595 return end;
596 }
597
598 char conversion = format.charAt(i);
599 i++;
600
601 flushLiteral(segments, literal);
602 segments
603 .add(
604 new Segment(
605 null,
606 argPosition,
607 new Flags(leftJustify, plusSign, spaceSign, zeroPad, alternate, grouping),
608 widthKind,
609 width,
610 precisionKind,
611 precision,
612 conversion,
613 null));
614 return i;
615 }
616
617
618
619
620
621
622
623
624 private static void emitVerbatim(List<Segment> segments, StringBuilder literal, Segment segment) {
625 boolean sideEffectFree = segment.argPosition == 0
626 && segment.widthKind != OPERAND_STAR
627 && segment.widthKind != OPERAND_STAR_POSITIONAL
628 && segment.precisionKind != OPERAND_STAR
629 && segment.precisionKind != OPERAND_STAR_POSITIONAL;
630 if (sideEffectFree) {
631 literal.append(segment.literal);
632 return;
633 }
634 flushLiteral(segments, literal);
635 segments.add(segment);
636 }
637
638
639
640
641
642 private static void emitError(
643 List<Segment> segments,
644 StringBuilder literal,
645 int widthKind,
646 int width,
647 String message) {
648 flushLiteral(segments, literal);
649 segments.add(new Segment(null, 0, NO_FLAGS, widthKind, width, OPERAND_NONE, -1, (char) 0, message));
650 }
651
652
653 private static void flushLiteral(List<Segment> segments, StringBuilder literal) {
654 if (literal.length() > 0) {
655 segments
656 .add(
657 new Segment(
658 literal.toString(),
659 0,
660 NO_FLAGS,
661 OPERAND_NONE,
662 -1,
663 OPERAND_NONE,
664 -1,
665 (char) 0,
666 null));
667 literal.setLength(0);
668 }
669 }
670
671
672
673
674
675 private static int starPositionEnd(String format, int i) {
676 int length = format.length();
677 int digitsEnd = i;
678 while (digitsEnd < length && isAsciiDigit(format.charAt(digitsEnd))) {
679 digitsEnd++;
680 }
681 if (digitsEnd > i && digitsEnd < length && format.charAt(digitsEnd) == '$') {
682 return digitsEnd + 1;
683 }
684 return i;
685 }
686
687
688
689
690
691 private static final class AwkPrintfFormatter {
692
693 private final Locale locale;
694 private final String convfmt;
695 private final String format;
696 private final Object[] args;
697 private final StringBuilder out;
698 private final char decimalSeparator;
699 private final char groupingSeparator;
700
701
702 private int argIndex;
703
704
705 private boolean sawPositional;
706
707
708 private boolean sawSequential;
709
710 AwkPrintfFormatter(Locale locale, String convfmt, String format, Object[] args) {
711 this.locale = locale;
712 this.convfmt = convfmt;
713 this.format = format;
714 this.args = args;
715 this.out = new StringBuilder(format.length() + 16);
716 LocaleSymbols symbols = localeSymbolsFor(locale);
717 this.decimalSeparator = symbols.decimalSeparator;
718 this.groupingSeparator = symbols.groupingSeparator;
719 }
720
721 String format() {
722 for (Segment segment : compiledFormat(format)) {
723 renderSegment(segment);
724 }
725 return out.toString();
726 }
727
728
729
730
731
732
733 private void renderSegment(Segment segment) {
734 Flags flags = segment.flags;
735 int width = -1;
736 if (segment.widthKind != OPERAND_NONE) {
737 if (segment.widthKind == OPERAND_FIXED) {
738 width = segment.width;
739 } else {
740 long dynamicWidth;
741 if (segment.widthKind == OPERAND_STAR) {
742
743
744
745 recordArgumentMode(false);
746 dynamicWidth = (long) JRT.toDouble(nextArg());
747 } else {
748
749
750 dynamicWidth = segment.width == 0 ? 0 : (long) JRT.toDouble(argAt(segment.width));
751 }
752 if (dynamicWidth < 0) {
753 flags = flags.withLeftJustify();
754 dynamicWidth = -dynamicWidth;
755 }
756 width = (int) Math.min(dynamicWidth, Integer.MAX_VALUE);
757 }
758 }
759 if (segment.parseError != null) {
760 throw new AwkRuntimeException(segment.parseError);
761 }
762 int precision = -1;
763 if (segment.precisionKind != OPERAND_NONE) {
764 if (segment.precisionKind == OPERAND_FIXED) {
765 precision = segment.precision;
766 } else {
767 long dynamicPrecision;
768 if (segment.precisionKind == OPERAND_STAR) {
769
770 recordArgumentMode(false);
771 dynamicPrecision = (long) JRT.toDouble(nextArg());
772 } else {
773
774 dynamicPrecision = segment.precision == 0 ? 0 : (long) JRT.toDouble(argAt(segment.precision));
775 }
776
777 if (dynamicPrecision >= 0) {
778 precision = (int) Math.min(dynamicPrecision, Integer.MAX_VALUE);
779 }
780 }
781 }
782 if (segment.literal != null) {
783
784
785 if (segment.argPosition > 0) {
786 recordArgumentMode(true);
787 requireArgumentIndex(segment.argPosition);
788 }
789 out.append(segment.literal);
790 return;
791 }
792 recordArgumentMode(segment.argPosition > 0);
793 Object arg = segment.argPosition > 0 ? argAt(segment.argPosition) : nextArg();
794 render(segment.conversion, flags, width, precision, arg);
795 }
796
797 private Object nextArg() {
798 if (argIndex >= args.length) {
799 throw new AwkRuntimeException("not enough arguments to satisfy format string `" + format + "'");
800 }
801 return args[argIndex++];
802 }
803
804 private Object argAt(int position) {
805 requireArgumentIndex(position);
806 return args[position - 1];
807 }
808
809
810
811
812
813
814 private void requireArgumentIndex(int position) {
815 if (position <= 0) {
816 throw new AwkRuntimeException("argument index with `$' must be > 0 in `" + format + "'");
817 }
818 if (position > args.length) {
819 throw new AwkRuntimeException(
820 "argument index " + position + " greater than total number of supplied arguments in `"
821 + format + "'");
822 }
823 }
824
825
826
827
828
829
830
831
832
833
834 private void recordArgumentMode(boolean positional) {
835 if (positional) {
836 sawPositional = true;
837 } else {
838 sawSequential = true;
839 }
840 if (sawPositional && sawSequential) {
841 throw new AwkRuntimeException("must use `count$' on all formats or none in `" + format + "'");
842 }
843 }
844
845 private void render(char conversion, Flags flags, int width, int precision, Object arg) {
846 switch (conversion) {
847 case 'c':
848 appendPadded(characterOf(arg), flags.leftJustify, false, width);
849 break;
850 case 's':
851 String s = toAwkString(arg, convfmt, locale);
852 if (precision >= 0 && s.codePointCount(0, s.length()) > precision) {
853
854
855 s = s.substring(0, s.offsetByCodePoints(0, precision));
856 }
857 appendPadded(s, flags.leftJustify, false, width);
858 break;
859 case 'd':
860 case 'i':
861 renderSignedInteger(flags, width, precision, arg);
862 break;
863 case 'u':
864 case 'o':
865 case 'x':
866 case 'X':
867 renderUnsignedInteger(conversion, flags, width, precision, arg);
868 break;
869 case 'e':
870 case 'E':
871 case 'f':
872 case 'F':
873 case 'g':
874 case 'G':
875 case 'a':
876 case 'A':
877 renderFloat(conversion, flags, width, precision, arg);
878 break;
879 default:
880
881 break;
882 }
883 }
884
885
886 private String characterOf(Object arg) {
887 if (arg == null) {
888 return NUL_STRING;
889 }
890 boolean numeric = arg instanceof Number || (arg instanceof StrNum && ((StrNum) arg).isNumber());
891 if (numeric) {
892 long code = (long) JRT.toDouble(arg);
893 StringBuilder sb = new StringBuilder(2);
894 if (code >= 0 && code <= Character.MAX_CODE_POINT) {
895 sb.appendCodePoint((int) code);
896 } else {
897 sb.append((char) code);
898 }
899 return sb.toString();
900 }
901 String s = arg.toString();
902 if (s.isEmpty()) {
903 return NUL_STRING;
904 }
905 StringBuilder sb = new StringBuilder(2);
906 sb.appendCodePoint(s.codePointAt(0));
907 return sb.toString();
908 }
909
910 private void renderSignedInteger(Flags flags, int width, int precision, Object arg) {
911 double d = JRT.toDouble(arg);
912 if (renderNonFinite('d', flags, width, d)) {
913 return;
914 }
915
916 boolean negative;
917 String magnitude;
918 if (arg instanceof Long || arg instanceof Integer || arg instanceof Short || arg instanceof Byte) {
919 long v = ((Number) arg).longValue();
920 negative = v < 0;
921 magnitude = negative ? Long.toUnsignedString(-v) : Long.toString(v);
922 } else if (d >= -TWO_POW_63 && d < TWO_POW_63) {
923 long v = (long) d;
924 negative = v < 0;
925 magnitude = negative ? Long.toUnsignedString(-v) : Long.toString(v);
926 } else {
927
928
929
930 BigInteger bi = new BigDecimal(d).toBigInteger();
931 negative = bi.signum() < 0;
932 magnitude = bi.abs().toString();
933 }
934
935 String sign = negative ? "-" : flags.plusSign ? "+" : flags.spaceSign ? " " : "";
936 appendInteger(sign, "", magnitude, flags, width, precision, isZeroMagnitude(magnitude));
937 }
938
939 private void renderUnsignedInteger(char conversion, Flags flags, int width, int precision, Object arg) {
940 double d = JRT.toDouble(arg);
941 if (renderNonFinite(conversion, flags, width, d)) {
942 return;
943 }
944
945 int radix = conversion == 'o' ? 8 : conversion == 'u' ? 10 : 16;
946 String magnitude;
947 if (arg instanceof Long || arg instanceof Integer || arg instanceof Short || arg instanceof Byte) {
948 magnitude = Long.toUnsignedString(((Number) arg).longValue(), radix);
949 } else if (d >= -TWO_POW_63 && d < TWO_POW_63) {
950 magnitude = Long.toUnsignedString((long) d, radix);
951 } else {
952
953 BigInteger bi = new BigDecimal(d).toBigInteger();
954 if (bi.signum() >= 0 && bi.compareTo(TWO_POW_64) < 0) {
955 magnitude = bi.toString(radix);
956 } else {
957
958
959
960 renderFloat('g', flags, width, precision, Double.valueOf(d));
961 return;
962 }
963 }
964 if (conversion == 'X') {
965 magnitude = magnitude.toUpperCase(Locale.ROOT);
966 }
967
968 boolean zeroValue = d == 0;
969 boolean zeroMagnitude = isZeroMagnitude(magnitude);
970 int actualPrecision = precision;
971 if (zeroMagnitude && precision == 0 && (flags.alternate || !zeroValue)) {
972
973
974
975
976 actualPrecision = 1;
977 }
978
979
980
981
982 String prefix = "";
983 if (flags.alternate && !zeroValue) {
984 if (conversion == 'x') {
985 prefix = "0x";
986 } else if (conversion == 'X') {
987 prefix = "0X";
988 } else if (conversion == 'o') {
989 prefix = "0";
990 }
991 }
992
993
994 Flags integerFlags = conversion == 'u' ? flags : flags.withoutGrouping();
995 appendInteger("", prefix, magnitude, integerFlags, width, actualPrecision, zeroMagnitude);
996 }
997
998
999
1000
1001
1002 private void appendInteger(
1003 String sign,
1004 String prefix,
1005 String magnitude,
1006 Flags flags,
1007 int width,
1008 int precision,
1009 boolean zeroMagnitude) {
1010 String digits = magnitude;
1011 if (precision == 0 && zeroMagnitude) {
1012
1013
1014 appendPadded("", flags.leftJustify, false, width);
1015 return;
1016 }
1017 if (precision > digits.length()) {
1018 digits = zeros(precision - digits.length()) + digits;
1019 }
1020 if (flags.grouping) {
1021 digits = groupDigits(digits);
1022 }
1023 String body = sign + prefix + digits;
1024 if (width > body.length() && flags.zeroPad && !flags.leftJustify && precision < 0) {
1025
1026 out.append(sign).append(prefix);
1027 out.append(zeros(width - body.length()));
1028 out.append(digits);
1029 return;
1030 }
1031 appendPadded(body, flags.leftJustify, false, width);
1032 }
1033
1034 private void renderFloat(char conversion, Flags flags, int width, int precision, Object arg) {
1035 double d = JRT.toDouble(arg);
1036 if (renderNonFinite(conversion, flags, width, d)) {
1037 return;
1038 }
1039 String magnitude = floatBody(conversion, flags, precision, d);
1040 if (magnitude == null) {
1041 return;
1042 }
1043 boolean negative = d < 0 || (d == 0 && Double.doubleToRawLongBits(d) != 0L);
1044 String sign = negative ? "-" : flags.plusSign ? "+" : flags.spaceSign ? " " : "";
1045
1046
1047 String prefix = "";
1048 if (magnitude.startsWith("0x") || magnitude.startsWith("0X")) {
1049 prefix = magnitude.substring(0, 2);
1050 magnitude = magnitude.substring(2);
1051 }
1052 String full = sign + prefix + magnitude;
1053 if (width > full.length() && flags.zeroPad && !flags.leftJustify) {
1054 out.append(sign).append(prefix);
1055 out.append(zeros(width - full.length()));
1056 out.append(magnitude);
1057 return;
1058 }
1059 appendPadded(full, flags.leftJustify, false, width);
1060 }
1061
1062
1063
1064
1065
1066
1067 private String floatBody(char conversion, Flags flags, int precision, double d) {
1068 double abs = Math.abs(d);
1069 switch (conversion) {
1070 case 'f':
1071 case 'F': {
1072 int p = precision < 0 ? 6 : precision;
1073 String s = decimalString(roundHalfEvenToScale(abs, p));
1074 if (flags.alternate && p == 0) {
1075 s = forceDecimalSeparator(s);
1076 }
1077 if (flags.grouping) {
1078 s = groupDigits(s);
1079 }
1080 return s;
1081 }
1082 case 'e':
1083 case 'E': {
1084 int p = precision < 0 ? 6 : precision;
1085 String s = scientific(abs, p);
1086 if (flags.alternate && p == 0) {
1087 int exponentStart = s.indexOf('e');
1088 s = forceDecimalSeparator(s.substring(0, exponentStart)) + s.substring(exponentStart);
1089 }
1090 return conversion == 'E' ? s.toUpperCase(Locale.ROOT) : s;
1091 }
1092 case 'g':
1093 case 'G': {
1094 int p = precision < 0 ? 6 : precision == 0 ? 1 : precision;
1095 String s = generalFloat(abs, p, flags.alternate, flags.grouping);
1096 return conversion == 'G' ? s.toUpperCase(Locale.ROOT) : s;
1097 }
1098 case 'a':
1099 case 'A':
1100 default: {
1101
1102
1103 StringBuilder spec = new StringBuilder("%");
1104 if (precision >= 0) {
1105 spec.append('.').append(precision);
1106 }
1107 spec.append(conversion);
1108 try {
1109 String s = String.format(locale, spec.toString(), Double.valueOf(abs));
1110 return s;
1111 } catch (IllegalFormatException e) {
1112 out.append(spec);
1113 return null;
1114 }
1115 }
1116 }
1117 }
1118
1119
1120 private String scientific(double abs, int precision) {
1121 BigDecimal mantissa;
1122 int exponent;
1123 if (abs == 0) {
1124 mantissa = BigDecimal.ZERO.setScale(precision);
1125 exponent = 0;
1126 } else {
1127 BigDecimal rounded = roundHalfEvenToSignificant(abs, precision + 1);
1128 exponent = rounded.precision() - rounded.scale() - 1;
1129 mantissa = rounded.movePointLeft(exponent).setScale(precision, RoundingMode.UNNECESSARY);
1130 }
1131 return decimalString(mantissa) + "e" + (exponent < 0 ? "-" : "+") + exponentDigits(Math.abs(exponent));
1132 }
1133
1134
1135 private String generalFloat(double abs, int precision, boolean alternate, boolean grouping) {
1136 if (abs == 0) {
1137 return alternate ? forceDecimalSeparator("0") + zeros(precision - 1) : "0";
1138 }
1139 BigDecimal rounded = roundHalfEvenToSignificant(abs, precision);
1140 int exponent = rounded.precision() - rounded.scale() - 1;
1141 if (exponent >= -4 && exponent < precision) {
1142 String s = decimalString(rounded.setScale(precision - 1 - exponent, RoundingMode.UNNECESSARY));
1143 s = alternate ? forceDecimalSeparator(s) : stripTrailingFractionZeros(s);
1144
1145
1146 return grouping ? groupDigits(s) : s;
1147 }
1148 String mantissa = decimalString(
1149 rounded.movePointLeft(exponent).setScale(precision - 1, RoundingMode.UNNECESSARY));
1150 mantissa = alternate ? forceDecimalSeparator(mantissa) : stripTrailingFractionZeros(mantissa);
1151 return mantissa + "e" + (exponent < 0 ? "-" : "+") + exponentDigits(Math.abs(exponent));
1152 }
1153
1154
1155
1156
1157
1158
1159 private boolean renderNonFinite(char conversion, Flags flags, int width, double d) {
1160 if (!Double.isNaN(d) && !Double.isInfinite(d)) {
1161 return false;
1162 }
1163 String body;
1164 if (Double.isNaN(d)) {
1165 body = flags.plusSign ? "+nan" : flags.spaceSign ? " nan" : "nan";
1166 } else if (d > 0) {
1167 body = flags.plusSign ? "+inf" : flags.spaceSign ? " inf" : "inf";
1168 } else {
1169 body = "-inf";
1170 }
1171 if (isUpperCaseConversion(conversion)) {
1172 body = body.toUpperCase(Locale.ROOT);
1173 }
1174
1175 appendPadded(body, flags.leftJustify, false, width);
1176 return true;
1177 }
1178
1179
1180 private String decimalString(BigDecimal value) {
1181 String s = value.toPlainString();
1182 return decimalSeparator == '.' ? s : s.replace('.', decimalSeparator);
1183 }
1184
1185
1186 private String groupDigits(String s) {
1187 int end = s.indexOf(decimalSeparator);
1188 if (end < 0) {
1189 end = s.length();
1190 }
1191 StringBuilder sb = new StringBuilder(s.length() + 8);
1192 for (int i = 0; i < end; i++) {
1193 sb.append(s.charAt(i));
1194 int remaining = end - 1 - i;
1195 if (remaining > 0 && remaining % 3 == 0 && isAsciiDigit(s.charAt(i))) {
1196 sb.append(groupingSeparator);
1197 }
1198 }
1199 sb.append(s, end, s.length());
1200 return sb.toString();
1201 }
1202
1203
1204
1205
1206
1207
1208 private String forceDecimalSeparator(String s) {
1209 return s.indexOf(decimalSeparator) < 0 ? s + decimalSeparator : s;
1210 }
1211
1212 private String stripTrailingFractionZeros(String s) {
1213 if (s.indexOf(decimalSeparator) < 0) {
1214 return s;
1215 }
1216 int end = s.length();
1217 while (end > 0 && s.charAt(end - 1) == '0') {
1218 end--;
1219 }
1220 if (end > 0 && s.charAt(end - 1) == decimalSeparator) {
1221 end--;
1222 }
1223 return s.substring(0, end);
1224 }
1225
1226 private void appendPadded(String body, boolean leftJustify, boolean zeroPad, int width) {
1227
1228
1229 int bodyLength = body.codePointCount(0, body.length());
1230 if (width <= bodyLength) {
1231 out.append(body);
1232 return;
1233 }
1234 int padLength = width - bodyLength;
1235 if (leftJustify) {
1236 out.append(body);
1237 appendSpaces(padLength);
1238 } else if (zeroPad) {
1239 out.append(zeros(padLength)).append(body);
1240 } else {
1241 appendSpaces(padLength);
1242 out.append(body);
1243 }
1244 }
1245
1246 private void appendSpaces(int count) {
1247 out.append(repeat(' ', count));
1248 }
1249 }
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262 private static BigDecimal roundHalfEvenToScale(double abs, int scale) {
1263 BigDecimal shortest = shortestDecimal(abs);
1264 if (shortest != null && roundsLikeExact(abs, shortest, shortest.scale() - scale, -scale)) {
1265 return shortest.setScale(scale, RoundingMode.HALF_EVEN);
1266 }
1267
1268 return new BigDecimal(abs).setScale(scale, RoundingMode.HALF_EVEN);
1269 }
1270
1271
1272
1273
1274
1275
1276
1277 private static BigDecimal roundHalfEvenToSignificant(double abs, int digits) {
1278 if (digits > 0) {
1279 BigDecimal shortest = shortestDecimal(abs);
1280 if (shortest != null) {
1281
1282
1283 int unitExponent = shortest.precision() - shortest.scale() - digits;
1284 if (roundsLikeExact(abs, shortest, shortest.precision() - digits, unitExponent)) {
1285 return shortest.round(new MathContext(digits, RoundingMode.HALF_EVEN));
1286 }
1287 }
1288 }
1289
1290 return new BigDecimal(abs).round(new MathContext(digits, RoundingMode.HALF_EVEN));
1291 }
1292
1293
1294
1295
1296
1297
1298 private static BigDecimal shortestDecimal(double abs) {
1299 if (!(abs >= Double.MIN_NORMAL) || abs > Double.MAX_VALUE) {
1300 return null;
1301 }
1302 return new BigDecimal(Double.toString(abs));
1303 }
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327 private static boolean roundsLikeExact(double abs, BigDecimal shortest, int remainderDigits, int unitExponent) {
1328 if (unitExponent < -307 || unitExponent > 308) {
1329
1330 return false;
1331 }
1332 if (Math.ulp(abs) * 100.0 >= Math.pow(10.0, unitExponent)) {
1333 return false;
1334 }
1335 if (remainderDigits <= 0) {
1336
1337
1338 return true;
1339 }
1340 String digits = shortest.unscaledValue().toString();
1341 if (remainderDigits > digits.length()) {
1342
1343
1344 return true;
1345 }
1346
1347
1348
1349
1350 char first = digits.charAt(digits.length() - remainderDigits);
1351 return first != '4' && first != '5';
1352 }
1353
1354
1355 private static String exponentDigits(int exponent) {
1356 String digits = Integer.toString(exponent);
1357 return digits.length() < 2 ? "0" + digits : digits;
1358 }
1359
1360 private static boolean isUpperCaseConversion(char conversion) {
1361 return conversion == 'X' || conversion == 'E' || conversion == 'F' || conversion == 'G' || conversion == 'A';
1362 }
1363
1364 private static boolean isZeroMagnitude(String magnitude) {
1365 for (int i = 0; i < magnitude.length(); i++) {
1366 if (magnitude.charAt(i) != '0') {
1367 return false;
1368 }
1369 }
1370 return true;
1371 }
1372
1373 private static boolean isAsciiDigit(char c) {
1374 return c >= '0' && c <= '9';
1375 }
1376
1377
1378
1379
1380
1381
1382 private static int parseInt(String s, int from, int to) {
1383 try {
1384 return Integer.parseInt(s.substring(from, to));
1385 } catch (NumberFormatException e) {
1386 return Integer.MAX_VALUE;
1387 }
1388 }
1389
1390 private static String zeros(int count) {
1391 return repeat('0', count);
1392 }
1393
1394 private static String repeat(char c, int count) {
1395 if (count <= 0) {
1396 return "";
1397 }
1398 char[] chars = new char[count];
1399 Arrays.fill(chars, c);
1400 return new String(chars);
1401 }
1402 }