blob: b462bf0cee0363960e7e35a972f73912a688ce73 [file]
// 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 spec
import (
"math"
"math/bits"
)
func isSigned[T Ints | Uints]() bool {
return T(0)-1 < 0
}
func maxVal[T Ints | Uints]() T {
if isSigned[T]() {
var zero T
switch any(zero).(type) {
case int8:
return any(int8(math.MaxInt8)).(T)
case int16:
return any(int16(math.MaxInt16)).(T)
case int32:
return any(int32(math.MaxInt32)).(T)
case int64:
return any(int64(math.MaxInt64)).(T)
}
panic("unhandled type")
}
return ^T(0)
}
func minVal[T Ints | Uints]() T {
if isSigned[T]() {
return ^maxVal[T]()
}
return 0
}
// saturate converts x to type T, with saturation.
func saturate[T Ints | Uints, U Ints | Uints](x T) U {
if isSigned[T]() {
return saturateS[U](int64(x))
}
return saturateU[U](uint64(x))
}
// saturateS converts signed x to type T, with saturation.
func saturateS[T Ints | Uints](x int64) T {
if int64(T(x)) == x && (x >= 0 || isSigned[T]()) {
// It's in range.
return T(x)
}
// Out of range
if x > 0 {
return maxVal[T]()
}
return minVal[T]()
}
// saturateU converts unsigned x to type T, with saturation.
func saturateU[T Ints | Uints](x uint64) T {
if x < uint64(maxVal[T]()) {
return T(x)
}
return maxVal[T]()
}
func addSaturated[T Ints | Uints](x, y T) T {
if isSigned[T]() {
return saturateS[T](addSaturatedSSS64(int64(x), int64(y)))
}
sum, carry := bits.Add64(uint64(x), uint64(y), 0)
if carry > 0 {
return maxVal[T]()
}
return saturateU[T](sum)
}
func addSaturatedSSS64(x, y int64) int64 {
sum := x + y
// Overflow can only happen if x and y have the same sign, and the sum has a
// different sign.
//
// (x ^ sum) & (y ^ sum) checks if the sign bit of sum matches neither x nor y.
if (x^sum)&(y^sum) < 0 {
if x > 0 {
return math.MaxInt64
}
return math.MinInt64
}
return sum
}
// subSaturated returns (x - y), saturated to the range of T.
func subSaturated[T Ints | Uints](x, y T) T {
if isSigned[T]() {
return saturateS[T](subSaturatedSSS64(int64(x), int64(y)))
}
diff, borrow := bits.Sub64(uint64(x), uint64(y), 0)
if borrow > 0 {
return minVal[T]()
}
return saturateU[T](diff)
}
func subSaturatedSSS64(x, y int64) int64 {
sub := x - y
// Overflow can only happen if x and y have different signs, and the result has a
// different sign than x.
//
// (x ^ y) & (x ^ sub) checks if the sign bit of x matches neither y nor sub.
if (x^y)&(x^sub) < 0 {
if x >= 0 {
return math.MaxInt64
}
return math.MinInt64
}
return sub
}
// scaleSaturated returns x * 2^s, saturated to the range of T.
func scaleSaturated[T Ints | Uints, S Ints | int](x T, s S) T {
n := S(elemBits[T]())
if s < 0 {
if s <= -n {
if isSigned[T]() && x < 0 {
return ^T(0)
}
return 0
}
return x >> uint(-s)
}
if s >= n {
if x == 0 {
return 0
}
if isSigned[T]() && x < 0 {
return minVal[T]()
}
return maxVal[T]()
}
res := x << uint(s)
if res>>uint(s) != x {
if isSigned[T]() && x < 0 {
return minVal[T]()
}
return maxVal[T]()
}
return res
}
func mulSaturatedUSS[X Uints, Y Ints](x X, y Y) Y {
// Expand to 64 bits and perform saturated multiplication
z := mulSaturatedUSS64(uint64(x), int64(y))
return saturateS[Y](z)
}
func mulSaturatedUSS64(x uint64, y int64) int64 {
if x == 0 || y == 0 {
return 0
}
// Get the absolute value of i as a uint64
var absI uint64
if y == math.MinInt64 {
absI = math.MaxInt64 + 1
} else if y < 0 {
absI = uint64(-y)
} else {
absI = uint64(y)
}
// 128-bit multiplication
hi, lo := bits.Mul64(x, absI)
if y > 0 {
// Positive result. Check for overflow.
if hi > 0 || lo >= math.MaxInt64 {
return math.MaxInt64
}
return int64(lo)
} else {
// Negative result. Check for underflow.
if hi > 0 || lo >= uint64(math.MaxInt64)+1 {
return math.MinInt64
}
return -int64(lo)
}
}
// clmul computes the carryless multiplication of a and b, returning the result
// split into high and low halves.
func clmul[E Uints](a, b E) (hi, lo E) {
n := elemBits[E]()
for i := range uint(n) {
if (a>>i)&1 == 1 {
// In Go, b >> n is 0 for an n-bit unsigned integer, so when i == 0,
// b >> (n - 0) evaluates to 0 while b << 0 leaves b unshifted.
lo ^= b << i
hi ^= b >> (uint(n) - i)
}
}
return
}
// fma32 returns x*y + z, computed with only one rounding.
func fma32(x, y, z float32) float32 {
// Because float32 has a 24-bit significand and exponent in [-149, 127], the
// product of two finite float32s has at most a 48-bit significand and
// exponent in [-298, 255], which is always exact in float64.
p := float64(x) * float64(y)
zf := float64(z)
s := p + zf
// p+zf performs one 53-bit rounding, and a naive conversion to float32
// performs a second. Specifically, if the true value of p + zf is
// slightly off the midpoint between two float32s, p + zf in float64 can
// round onto that exact float32 midpoint (whose low 28 float64
// significand bits are 0, so bit 0 is even), and the subsequent float32
// round-to-even conversion may round in the wrong direction. We
// implement round-to-odd per Boldo & Melquiond to counteract this.
if !math.IsInf(s, 0) && !math.IsNaN(s) && math.Float64bits(s)&1 == 0 {
// s is even, so we need to nudge s to perform round-to-odd. Compute
// the exact round-off error for the p+zf addition using Fast2Sum.
hi, lo := p, zf
if math.Abs(hi) < math.Abs(lo) {
hi, lo = lo, hi
}
if err := (hi - s) + lo; err != 0 {
// Nudge s by one float64 ULP toward odd. Choose the odd
// neighbor in the direction of err to keep s on the same side
// of the midpoint as the true value of p + zf.
if (err > 0) == (s > 0) {
s = math.Float64frombits(math.Float64bits(s) | 1)
} else {
s = math.Float64frombits(math.Float64bits(s) - 1)
}
}
}
return float32(s)
}