From fda93f643efa5dd6aafde1df7f086862a5dad9e3 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 16 Apr 2021 08:27:16 +0200 Subject: [PATCH] log: fix formatting of big.Int (#22679) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * log: fix formatting of big.Int The implementation of formatLogfmtBigInt had two issues: it crashed when the number was actually large enough to hit the big integer case, and modified the big.Int while formatting it. * log: don't call FormatLogfmtInt64 for int16 * log: separate from decimals back, not front Co-authored-by: Péter Szilágyi --- log/format.go | 58 +++++++++++++++++++++++++++------------------- log/format_test.go | 20 ++++++++++++++++ 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/log/format.go b/log/format.go index 0667921528..baf8fddac0 100644 --- a/log/format.go +++ b/log/format.go @@ -359,11 +359,16 @@ func formatLogfmtValue(value interface{}, term bool) string { return strconv.FormatFloat(float64(v), floatFormat, 3, 64) case float64: return strconv.FormatFloat(v, floatFormat, 3, 64) - case int8, uint8: - return fmt.Sprintf("%d", value) - case int: - return FormatLogfmtInt64(int64(v)) + case int8: + return strconv.FormatInt(int64(v), 10) + case uint8: + return strconv.FormatInt(int64(v), 10) case int16: + return strconv.FormatInt(int64(v), 10) + case uint16: + return strconv.FormatInt(int64(v), 10) + // Larger integers get thousands separators. + case int: return FormatLogfmtInt64(int64(v)) case int32: return FormatLogfmtInt64(int64(v)) @@ -371,8 +376,6 @@ func formatLogfmtValue(value interface{}, term bool) string { return FormatLogfmtInt64(v) case uint: return FormatLogfmtUint64(uint64(v)) - case uint16: - return FormatLogfmtUint64(uint64(v)) case uint32: return FormatLogfmtUint64(uint64(v)) case uint64: @@ -384,7 +387,7 @@ func formatLogfmtValue(value interface{}, term bool) string { } } -// FormatLogfmtInt64 formats a potentially big number in a friendlier split format. +// FormatLogfmtInt64 formats n with thousand separators. func FormatLogfmtInt64(n int64) string { if n < 0 { return formatLogfmtUint64(uint64(-n), true) @@ -392,7 +395,7 @@ func FormatLogfmtInt64(n int64) string { return formatLogfmtUint64(uint64(n), false) } -// FormatLogfmtUint64 formats a potentially big number in a friendlier split format. +// FormatLogfmtUint64 formats n with thousand separators. func FormatLogfmtUint64(n uint64) string { return formatLogfmtUint64(n, false) } @@ -431,31 +434,38 @@ func formatLogfmtUint64(n uint64, neg bool) string { return string(out[i+1:]) } -var big1000 = big.NewInt(1000) - -// formatLogfmtBigInt formats a potentially gigantic number in a friendlier split -// format. +// formatLogfmtBigInt formats n with thousand separators. func formatLogfmtBigInt(n *big.Int) string { - // Most number don't need fancy handling, just downcast if n.IsUint64() { return FormatLogfmtUint64(n.Uint64()) } if n.IsInt64() { return FormatLogfmtInt64(n.Int64()) } - // Ok, huge number needs huge effort - groups := make([]string, 0, 8) // random initial size to cover most cases - for n.Cmp(big1000) >= 0 { - _, mod := n.DivMod(n, big1000, nil) - groups = append(groups, fmt.Sprintf("%03d", mod)) - } - groups = append(groups, n.String()) - last := len(groups) - 1 - for i := 0; i < len(groups)/2; i++ { - groups[i], groups[last-i] = groups[last-i], groups[i] + var ( + text = n.String() + buf = make([]byte, len(text)+len(text)/3) + comma = 0 + i = len(buf) - 1 + ) + for j := len(text) - 1; j >= 0; j, i = j-1, i-1 { + c := text[j] + + switch { + case c == '-': + buf[i] = c + case comma == 3: + buf[i] = ',' + i-- + comma = 0 + fallthrough + default: + buf[i] = c + comma++ + } } - return strings.Join(groups, ",") + return string(buf[i+1:]) } // escapeString checks if the provided string needs escaping/quoting, and diff --git a/log/format_test.go b/log/format_test.go index 348b265c9b..d7e0a95768 100644 --- a/log/format_test.go +++ b/log/format_test.go @@ -2,6 +2,7 @@ package log import ( "math" + "math/big" "math/rand" "testing" ) @@ -58,6 +59,25 @@ func TestPrettyUint64(t *testing.T) { } } +func TestPrettyBigInt(t *testing.T) { + tests := []struct { + int string + s string + }{ + {"111222333444555678999", "111,222,333,444,555,678,999"}, + {"-111222333444555678999", "-111,222,333,444,555,678,999"}, + {"11122233344455567899900", "11,122,233,344,455,567,899,900"}, + {"-11122233344455567899900", "-11,122,233,344,455,567,899,900"}, + } + + for _, tt := range tests { + v, _ := new(big.Int).SetString(tt.int, 10) + if have := formatLogfmtBigInt(v); have != tt.s { + t.Errorf("invalid output %s, want %s", have, tt.s) + } + } +} + var sink string func BenchmarkPrettyInt64Logfmt(b *testing.B) {