From fda93f643efa5dd6aafde1df7f086862a5dad9e3 Mon Sep 17 00:00:00 2001
From: Felix Lange <fjl@twurst.com>
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 <peterke@gmail.com>
---
 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 066792152..baf8fddac 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 348b265c9..d7e0a9576 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) {
-- 
GitLab