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