blob: 66f2de8d0cb38b135b92374f130b1d115aad70d8 [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
}
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)
}
}