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>
This commit is contained in:
Noam Cohen
2026-03-09 12:08:36 +02:00
committed by Gopher Robot
parent 96d6d38872
commit a8f99ef1f6
4 changed files with 48 additions and 7 deletions

28
src/go/printer/math.go Normal file
View File

@@ -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))
}

View File

@@ -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++
}

View File

@@ -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
}

View File

@@ -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
}