aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNoam Cohen <noam@noam.me>2026-03-09 12:08:36 +0200
committerGopher Robot <gobot@golang.org>2026-03-11 13:27:47 -0700
commita8f99ef1f6e550d5d73e9c8f792337ad001bdcf4 (patch)
treefa843d1d45c321015c7f049d411210745ad011db /src
parent96d6d38872a56eba26cd2e3dbd65bab138f9c599 (diff)
downloadgo-a8f99ef1f6e550d5d73e9c8f792337ad001bdcf4.tar.xz
go/printer: use architecture-independent math for alignment decisions
The `exprList` function uses a geometric mean of expression sizes to decide whether to break column alignment in composite literals. Previously, this was computed using `math.Exp` and `math.Log`, which are not guaranteed to produce bit-for-bit identical results across CPU architectures. For example, `math.Exp(math.Log(95))` returns exactly 95.0 on arm64 but 94.999... on amd64, which can flip the alignment decision. Replace math operations with approximations that are architecture-independent. The approximations are sufficient for this heuristic and are guaranteed to produce identical formatting on all architectures. Fixes #77959 Change-Id: I78d00d85fd62859803d14d619b4a45e2e5081499 Reviewed-on: https://go-review.googlesource.com/c/go/+/752862 Auto-Submit: Alan Donovan <adonovan@google.com> Reviewed-by: Russ Cox <rsc@golang.org> Reviewed-by: Robert Griesemer <gri@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'src')
-rw-r--r--src/go/printer/math.go28
-rw-r--r--src/go/printer/nodes.go13
-rw-r--r--src/go/printer/testdata/alignment.golden7
-rw-r--r--src/go/printer/testdata/alignment.input7
4 files changed, 48 insertions, 7 deletions
diff --git a/src/go/printer/math.go b/src/go/printer/math.go
new file mode 100644
index 0000000000..543a9e99d5
--- /dev/null
+++ b/src/go/printer/math.go
@@ -0,0 +1,28 @@
+// Copyright 2026 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package printer
+
+import "math"
+
+// log2ish returns a crude approximation to log₂(x).
+// The result is only used for heuristic alignment decisions and should
+// not be used where precision matters.
+// The approximation is guaranteed to produce identical results
+// across all architectures.
+func log2ish(x float64) float64 {
+ f, e := math.Frexp(x)
+ return float64(e) + 2*(f-1)
+}
+
+// exp2ish returns a crude approximation to 2**x.
+// The result is only used for heuristic alignment decisions and should
+// not be used where precision matters.
+// The approximation is guaranteed to produce identical results
+// across all architectures.
+func exp2ish(x float64) float64 {
+ n := math.Floor(x)
+ f := x - n
+ return math.Ldexp(1+f, int(n))
+}
diff --git a/src/go/printer/nodes.go b/src/go/printer/nodes.go
index a295a68d6f..6c7a2a031a 100644
--- a/src/go/printer/nodes.go
+++ b/src/go/printer/nodes.go
@@ -11,7 +11,6 @@ package printer
import (
"go/ast"
"go/token"
- "math"
"strconv"
"strings"
"unicode"
@@ -183,9 +182,9 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp
// We use the ratio between the geometric mean of the previous key sizes and
// the current size to determine if there should be a break in the alignment.
- // To compute the geometric mean we accumulate the ln(size) values (lnsum)
+ // To compute the geometric mean we accumulate the log₂(size) values (log2sum)
// and the number of sizes included (count).
- lnsum := 0.0
+ log2sum := 0.0
count := 0
// print all list elements
@@ -228,8 +227,8 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp
if count == 0 || prevSize <= smallSize && size <= smallSize {
useFF = false
} else {
- const r = 2.5 // threshold
- geomean := math.Exp(lnsum / float64(count)) // count > 0
+ const r = 2.5 // threshold
+ geomean := exp2ish(log2sum / float64(count)) // count > 0
ratio := float64(size) / geomean
useFF = r*ratio <= 1 || r <= ratio
}
@@ -260,7 +259,7 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp
// the section), reset the geomean variables since we are
// starting a new group of elements with the next element.
if nbreaks > 1 {
- lnsum = 0
+ log2sum = 0
count = 0
}
}
@@ -284,7 +283,7 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp
}
if size > 0 {
- lnsum += math.Log(float64(size))
+ log2sum += log2ish(float64(size))
count++
}
diff --git a/src/go/printer/testdata/alignment.golden b/src/go/printer/testdata/alignment.golden
index 96086ed906..74223e9856 100644
--- a/src/go/printer/testdata/alignment.golden
+++ b/src/go/printer/testdata/alignment.golden
@@ -170,3 +170,10 @@ var _ = S{
F1____: []string{},
F2: []string{},
}
+
+// Issue #77959: formatting must be consistent across architectures.
+var _ = []any{
+ (expr_size_95_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)(0), // 0
+ (expr_size_38_xxxxxxxxxxxxxxxxxxxx)(0), // 1
+ (*expr_size_126_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)(nil), // 2
+}
diff --git a/src/go/printer/testdata/alignment.input b/src/go/printer/testdata/alignment.input
index 323d2689e0..61c3ae2a98 100644
--- a/src/go/printer/testdata/alignment.input
+++ b/src/go/printer/testdata/alignment.input
@@ -177,3 +177,10 @@ var _ = S{
},
F2: []string{},
}
+
+// Issue #77959: formatting must be consistent across architectures.
+var _ = []any{
+ (expr_size_95_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)(0), // 0
+ (expr_size_38_xxxxxxxxxxxxxxxxxxxx)(0), // 1
+ (*expr_size_126_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)(nil), // 2
+}