Skip to content

Commit 29fd570

Browse files
authored
Fix floating point formatting, google#799 (google#801)
* Fix floating point formatting, google#799
1 parent b9a7468 commit 29fd570

File tree

2 files changed

+67
-30
lines changed

2 files changed

+67
-30
lines changed

stdlib/std.jsonnet

+45-30
Original file line numberDiff line numberDiff line change
@@ -340,10 +340,10 @@ limitations under the License.
340340
else if c == ' ' then
341341
consume(str, j + 1, v { blank: true })
342342
else if c == '+' then
343-
consume(str, j + 1, v { sign: true })
343+
consume(str, j + 1, v { plus: true })
344344
else
345345
{ i: j, v: v };
346-
consume(str, i, { alt: false, zero: false, left: false, blank: false, sign: false });
346+
consume(str, i, { alt: false, zero: false, left: false, blank: false, plus: false });
347347

348348
local try_parse_field_width(str, i) =
349349
if i < std.length(str) && str[i] == '*' then
@@ -483,23 +483,38 @@ limitations under the License.
483483
local pad_right(str, w, s) =
484484
str + padding(w - std.length(str), s);
485485

486-
// Render an integer (e.g., decimal or octal).
487-
local render_int(n__, min_chars, min_digits, blank, sign, radix, zero_prefix) =
488-
local n_ = std.abs(n__);
489-
local aux(n) =
490-
if n == 0 then
491-
zero_prefix
486+
// Render a sign & magnitude integer (radix ranges from decimal to binary).
487+
// neg should be a boolean, and when true indicates that we should render a negative number.
488+
// mag must always be a whole number >= 0, it's the magnitude of the integer to render
489+
// min_chars must be a whole number >= 0
490+
// It is the field width, i.e. std.length() of the result should be >= min_chars
491+
// min_digits must be a whole number >= 0. It's the number of zeroes to pad with.
492+
// blank must be a boolean, if true adds an additional ' ' in front of a positive number, so
493+
// that it is aligned with negative numbers with the same number of digits.
494+
// plus must be a boolean, if true adds a '+' in front of a postive number, so that it is
495+
// aligned with negative numbers with the same number of digits. This takes precedence over
496+
// blank, if both are true.
497+
// radix must be a whole number >1 and <= 10. It is the base of the system of numerals.
498+
// zero_prefix is a string prefixed before the sign to all numbers that are not 0.
499+
local render_int(neg, mag, min_chars, min_digits, blank, plus, radix, zero_prefix) =
500+
// dec is the minimal string needed to represent the number as text.
501+
local dec =
502+
if mag == 0 then
503+
'0'
492504
else
493-
aux(std.floor(n / radix)) + (n % radix);
494-
local dec = if std.floor(n_) == 0 then '0' else aux(std.floor(n_));
495-
local neg = n__ < 0;
496-
local zp = min_chars - (if neg || blank || sign then 1 else 0);
505+
local aux(n) =
506+
if n == 0 then
507+
zero_prefix
508+
else
509+
aux(std.floor(n / radix)) + (n % radix);
510+
aux(mag);
511+
local zp = min_chars - (if neg || blank || plus then 1 else 0);
497512
local zp2 = std.max(zp, min_digits);
498513
local dec2 = pad_left(dec, zp2, '0');
499-
(if neg then '-' else if sign then '+' else if blank then ' ' else '') + dec2;
514+
(if neg then '-' else if plus then '+' else if blank then ' ' else '') + dec2;
500515

501516
// Render an integer in hexadecimal.
502-
local render_hex(n__, min_chars, min_digits, blank, sign, add_zerox, capitals) =
517+
local render_hex(n__, min_chars, min_digits, blank, plus, add_zerox, capitals) =
503518
local numerals = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
504519
+ if capitals then ['A', 'B', 'C', 'D', 'E', 'F']
505520
else ['a', 'b', 'c', 'd', 'e', 'f'];
@@ -511,12 +526,12 @@ limitations under the License.
511526
aux(std.floor(n / 16)) + numerals[n % 16];
512527
local hex = if std.floor(n_) == 0 then '0' else aux(std.floor(n_));
513528
local neg = n__ < 0;
514-
local zp = min_chars - (if neg || blank || sign then 1 else 0)
529+
local zp = min_chars - (if neg || blank || plus then 1 else 0)
515530
- (if add_zerox then 2 else 0);
516531
local zp2 = std.max(zp, min_digits);
517532
local hex2 = (if add_zerox then (if capitals then '0X' else '0x') else '')
518533
+ pad_left(hex, zp2, '0');
519-
(if neg then '-' else if sign then '+' else if blank then ' ' else '') + hex2;
534+
(if neg then '-' else if plus then '+' else if blank then ' ' else '') + hex2;
520535

521536
local strip_trailing_zero(str) =
522537
local aux(str, i) =
@@ -530,35 +545,35 @@ limitations under the License.
530545
aux(str, std.length(str) - 1);
531546

532547
// Render floating point in decimal form
533-
local render_float_dec(n__, zero_pad, blank, sign, ensure_pt, trailing, prec) =
548+
local render_float_dec(n__, zero_pad, blank, plus, ensure_pt, trailing, prec) =
534549
local n_ = std.abs(n__);
535550
local whole = std.floor(n_);
536551
local dot_size = if prec == 0 && !ensure_pt then 0 else 1;
537552
local zp = zero_pad - prec - dot_size;
538-
local str = render_int(std.sign(n__) * whole, zp, 0, blank, sign, 10, '');
553+
local str = render_int(n__ < 0, whole, zp, 0, blank, plus, 10, '');
539554
if prec == 0 then
540555
str + if ensure_pt then '.' else ''
541556
else
542557
local frac = std.floor((n_ - whole) * std.pow(10, prec) + 0.5);
543558
if trailing || frac > 0 then
544-
local frac_str = render_int(frac, prec, 0, false, false, 10, '');
559+
local frac_str = render_int(false, frac, prec, 0, false, false, 10, '');
545560
str + '.' + if !trailing then strip_trailing_zero(frac_str) else frac_str
546561
else
547562
str;
548563

549564
// Render floating point in scientific form
550-
local render_float_sci(n__, zero_pad, blank, sign, ensure_pt, trailing, caps, prec) =
565+
local render_float_sci(n__, zero_pad, blank, plus, ensure_pt, trailing, caps, prec) =
551566
local exponent = if n__ == 0 then 0 else std.floor(std.log(std.abs(n__)) / std.log(10));
552567
local suff = (if caps then 'E' else 'e')
553-
+ render_int(exponent, 3, 0, false, true, 10, '');
568+
+ render_int(exponent < 0, std.abs(exponent), 3, 0, false, true, 10, '');
554569
local mantissa = if exponent == -324 then
555570
// Avoid a rounding error where std.pow(10, -324) is 0
556571
// -324 is the smallest exponent possible.
557572
n__ * 10 / std.pow(10, exponent + 1)
558573
else
559574
n__ / std.pow(10, exponent);
560575
local zp2 = zero_pad - std.length(suff);
561-
render_float_dec(mantissa, zp2, blank, sign, ensure_pt, trailing, prec) + suff;
576+
render_float_dec(mantissa, zp2, blank, plus, ensure_pt, trailing, prec) + suff;
562577

563578
// Render a value with an arbitrary format code.
564579
local format_code(val, code, fw, prec_or_null, i) =
@@ -573,24 +588,24 @@ limitations under the License.
573588
error 'Format required number at '
574589
+ i + ', got ' + std.type(val)
575590
else
576-
render_int(val, zp, iprec, cflags.blank, cflags.sign, 10, '')
591+
render_int(val <= -1, std.floor(std.abs(val)), zp, iprec, cflags.blank, cflags.plus, 10, '')
577592
else if code.ctype == 'o' then
578593
if std.type(val) != 'number' then
579594
error 'Format required number at '
580595
+ i + ', got ' + std.type(val)
581596
else
582597
local zero_prefix = if cflags.alt then '0' else '';
583-
render_int(val, zp, iprec, cflags.blank, cflags.sign, 8, zero_prefix)
598+
render_int(val <= -1, std.floor(std.abs(val)), zp, iprec, cflags.blank, cflags.plus, 8, zero_prefix)
584599
else if code.ctype == 'x' then
585600
if std.type(val) != 'number' then
586601
error 'Format required number at '
587602
+ i + ', got ' + std.type(val)
588603
else
589-
render_hex(val,
604+
render_hex(std.floor(val),
590605
zp,
591606
iprec,
592607
cflags.blank,
593-
cflags.sign,
608+
cflags.plus,
594609
cflags.alt,
595610
code.caps)
596611
else if code.ctype == 'f' then
@@ -601,7 +616,7 @@ limitations under the License.
601616
render_float_dec(val,
602617
zp,
603618
cflags.blank,
604-
cflags.sign,
619+
cflags.plus,
605620
cflags.alt,
606621
true,
607622
fpprec)
@@ -613,7 +628,7 @@ limitations under the License.
613628
render_float_sci(val,
614629
zp,
615630
cflags.blank,
616-
cflags.sign,
631+
cflags.plus,
617632
cflags.alt,
618633
true,
619634
code.caps,
@@ -628,7 +643,7 @@ limitations under the License.
628643
render_float_sci(val,
629644
zp,
630645
cflags.blank,
631-
cflags.sign,
646+
cflags.plus,
632647
cflags.alt,
633648
cflags.alt,
634649
code.caps,
@@ -638,7 +653,7 @@ limitations under the License.
638653
render_float_dec(val,
639654
zp,
640655
cflags.blank,
641-
cflags.sign,
656+
cflags.plus,
642657
cflags.alt,
643658
cflags.alt,
644659
fpprec - digits_before_pt)

test_suite/format.jsonnet

+22
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,27 @@ std.assertEqual(std.format('thing-%-5.3d', [10.3]), 'thing-010 ') &&
5353
std.assertEqual(std.format('thing-%#-5.3d', [10.3]), 'thing-010 ') &&
5454
std.assertEqual(std.format('thing-%#-5.3i', [10.3]), 'thing-010 ') &&
5555
std.assertEqual(std.format('thing-%#-5.3u', [10.3]), 'thing-010 ') &&
56+
std.assertEqual(std.format('thing-%5.3d', [-10.3]), 'thing- -010') &&
57+
std.assertEqual(std.format('thing-%+5.3d', [-10.3]), 'thing- -010') &&
58+
std.assertEqual(std.format('thing-%+-5.3d', [-10.3]), 'thing--010 ') &&
59+
std.assertEqual(std.format('thing-%-5.3d', [-10.3]), 'thing--010 ') &&
60+
std.assertEqual(std.format('thing-%#-5.3d', [-10.3]), 'thing--010 ') &&
61+
std.assertEqual(std.format('thing-%#-5.3i', [-10.3]), 'thing--010 ') &&
62+
std.assertEqual(std.format('thing-%#-5.3u', [-10.3]), 'thing--010 ') &&
63+
std.assertEqual(std.format('thing-%5.3d', [0.3]), 'thing- 000') &&
64+
std.assertEqual(std.format('thing-%+5.3d', [0.3]), 'thing- +000') &&
65+
std.assertEqual(std.format('thing-%+-5.3d', [0.3]), 'thing-+000 ') &&
66+
std.assertEqual(std.format('thing-%-5.3d', [0.3]), 'thing-000 ') &&
67+
std.assertEqual(std.format('thing-%#-5.3d', [0.3]), 'thing-000 ') &&
68+
std.assertEqual(std.format('thing-%#-5.3i', [0.3]), 'thing-000 ') &&
69+
std.assertEqual(std.format('thing-%#-5.3u', [0.3]), 'thing-000 ') &&
70+
std.assertEqual(std.format('thing-%5.3d', [-0.3]), 'thing- 000') &&
71+
std.assertEqual(std.format('thing-%+5.3d', [-0.3]), 'thing- +000') &&
72+
std.assertEqual(std.format('thing-%+-5.3d', [-0.3]), 'thing-+000 ') &&
73+
std.assertEqual(std.format('thing-%-5.3d', [-0.3]), 'thing-000 ') &&
74+
std.assertEqual(std.format('thing-%#-5.3d', [-0.3]), 'thing-000 ') &&
75+
std.assertEqual(std.format('thing-%#-5.3i', [-0.3]), 'thing-000 ') &&
76+
std.assertEqual(std.format('thing-%#-5.3u', [-0.3]), 'thing-000 ') &&
5677

5778
// o
5879
std.assertEqual(std.format('thing-%o', [10]), 'thing-12') &&
@@ -189,6 +210,7 @@ std.assertEqual(std.format('%-12.4f', [910.3]), '910.3000 ') &&
189210
std.assertEqual(std.format('%#.0f', [910.3]), '910.') &&
190211
std.assertEqual(std.format('%#.0f', [910]), '910.') &&
191212
std.assertEqual(std.format('%.3f', [1000000001]), '1000000001.000') &&
213+
std.assertEqual(std.format('%f', [-0.1]), '-0.100000') &&
192214

193215
// g
194216
std.assertEqual(std.format('%#.3g', [1000000001]), '1.00e+09') &&

0 commit comments

Comments
 (0)