diff options
| author | Noam Cohen <noam@noam.me> | 2026-03-09 12:08:36 +0200 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2026-03-11 13:27:47 -0700 |
| commit | a8f99ef1f6e550d5d73e9c8f792337ad001bdcf4 (patch) | |
| tree | fa843d1d45c321015c7f049d411210745ad011db /src | |
| parent | 96d6d38872a56eba26cd2e3dbd65bab138f9c599 (diff) | |
| download | go-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.go | 28 | ||||
| -rw-r--r-- | src/go/printer/nodes.go | 13 | ||||
| -rw-r--r-- | src/go/printer/testdata/alignment.golden | 7 | ||||
| -rw-r--r-- | src/go/printer/testdata/alignment.input | 7 |
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 +} |
