mirror of
https://github.com/golang/go.git
synced 2026-04-03 17:59:55 +09:00
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:
28
src/go/printer/math.go
Normal file
28
src/go/printer/math.go
Normal 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))
|
||||
}
|
||||
@@ -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++
|
||||
}
|
||||
|
||||
|
||||
7
src/go/printer/testdata/alignment.golden
vendored
7
src/go/printer/testdata/alignment.golden
vendored
@@ -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
|
||||
}
|
||||
|
||||
7
src/go/printer/testdata/alignment.input
vendored
7
src/go/printer/testdata/alignment.input
vendored
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user