Let's assume the runtime representation case, as it's the most flexible. You'd need to do an assignability check to compare it to a typed number. Keep LHS as the interface, and RHS as the untyped constant.
That means following the type pointer of LHS, switching on its underlying type (with 15 valid possibilities [1]) or similar, and then casting either RHS to LHS's type, or LHS to the untyped representation, and finally doing the equality check. Something like this (modulo choice of representation and possible optimizations):
import ("math/big"; "reflect")
type untypedInt struct { i *big.Int }
func (x untypedInt) equals(y any) bool {
val := reflect.ValueOf(y)
if val.Type() == reflect.TypeOf(x) {
return x.i.Cmp(val.Interface().(untypedInt).i) == 0
} else if val.CanInt() {
if !x.i.IsInt64() { return false }
return x.i.Int64() == val.Int()
} else if val.CanUint() {
if !x.i.IsUint64() { return false }
return x.i.Uint64() == val.Uint()
} else {
var yf float64
if val.CanFloat() {
yf = val.Float()
} else if val.CanComplex() {
yc := val.Complex()
if imag(yc) != 0 { return false }
yf = real(yc)
} else { return false }
xf, acc := x.i.Float64()
if acc != big.Exact { return false }
return xf == yf
}
}
[1]: Untyped integer constants can be compared with any of uint8..uint64, int8..int64, int, uint, uintptr, float32, float64, complex64, or complex128