如何比较通用的数值类型

huangapple go评论98阅读模式
英文:

How to compare generic number type

问题

我有一个验证函数Positive,它能工作但看起来很丑陋。

type Positiver interface {
	decimal.Decimal | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}

//nolint:cyclop
func Positive[T Positiver](value T, name string, errs *[]error) {
	addError := func() {
		err := fmt.Errorf(`%s %w, but it's %v`, name, failures.ShouldBePositive, value)
		*errs = append(*errs, err)
	}

	const prescision = 8

	switch val := any(value).(type) {
	case decimal.Decimal:
		if val.IsNegative() || val.IsZero() {
			err := fmt.Errorf(`%s %w, but it's %s`, name, failures.ShouldBePositive, val.StringFixedBank(prescision))
			*errs = append(*errs, err)
		}

		return
	case int:
		if val <= 0 {
			addError()
		}
	case int64:
		if val <= 0 {
			addError()
		}
	case int32:
		if val <= 0 {
			addError()
		}
	case int16:
		if val <= 0 {
			addError()
		}
	case int8:
		if val <= 0 {
			addError()
		}
	case uint:
		if val <= 0 {
			addError()
		}
	case uint64:
		if val <= 0 {
			addError()
		}
	case uint32:
		if val <= 0 {
			addError()
		}
	case uint16:
		if val <= 0 {
			addError()
		}
	case uint8:
		if val <= 0 {
			addError()
		}
	case float32:
		if val <= 0 {
			addError()
		}
	case float64:
		if val <= 0 {
			addError()
		}
	default:
		panic(fmt.Sprintf(`%T is not supported type`, val))
	}
}

我知道使用[]error是一个不好的方法,最好是返回一个封装的错误。但这是一个兼容性问题。

我尝试像这样做:

func Positive[T Positiver](value T, name string, errs *[]error) {
	switch val := any(value).(type) {
	case decimal.Decimal:
		if val.IsNegative() || val.IsZero() {
			err := fmt.Errorf(`%s %w, but it's not`, name, failures.ShouldBePositive)
			*errs = append(*errs, err)
		}

		return
	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
		if val.(int64) < 0 {
			err := fmt.Errorf(`%s %w, but it's not`, name, failures.ShouldBePositive)
			*errs = append(*errs, err)
		}

		return
	case float32, float64:
		if val.(float64) < 0 {
			err := fmt.Errorf(`%s %w, but it's not`, name, failures.ShouldBePositive)
			*errs = append(*errs, err)
		}

		return
	default:
		panic(fmt.Sprintf(`%T is not supported type`, val))
	}
}

但这种方法给我返回了一个错误:
test panicked: interface conversion: interface {} is int, not int64

有什么更好的方法来比较值是否大于零?

英文:

I have a validation function Positive, it's works but looks ugly.

type Positiver interface {
decimal.Decimal | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}
//nolint:cyclop
func Positive[T Positiver](value T, name string, errs *[]error) {
addError := func() {
err := fmt.Errorf(`%s %w, but it&#39;s %v`, name, failures.ShouldBePositive, value)
*errs = append(*errs, err)
}
const prescision = 8
switch val := any(value).(type) {
case decimal.Decimal:
if val.IsNegative() || val.IsZero() {
err := fmt.Errorf(`%s %w, but it&#39;s %s`, name, failures.ShouldBePositive, val.StringFixedBank(prescision))
*errs = append(*errs, err)
}
return
case int:
if val &lt;= 0 {
addError()
}
case int64:
if val &lt;= 0 {
addError()
}
case int32:
if val &lt;= 0 {
addError()
}
case int16:
if val &lt;= 0 {
addError()
}
case int8:
if val &lt;= 0 {
addError()
}
case uint:
if val &lt;= 0 {
addError()
}
case uint64:
if val &lt;= 0 {
addError()
}
case uint32:
if val &lt;= 0 {
addError()
}
case uint16:
if val &lt;= 0 {
addError()
}
case uint8:
if val &lt;= 0 {
addError()
}
case float32:
if val &lt;= 0 {
addError()
}
case float64:
if val &lt;= 0 {
addError()
}
default:
panic(fmt.Sprintf(`%T is not supported type`, val))
}
}

I know that is bad approach to use []error, it's better just to return a wrapped error.
But it's a compatibility issue.

I tried to do like this:

func Positive[T Positiver](value T, name string, errs *[]error) {
switch val := any(value).(type) {
case decimal.Decimal:
if val.IsNegative() || val.IsZero() {
err := fmt.Errorf(`%s %w, but it&#39;s not`, name, failures.ShouldBePositive)
*errs = append(*errs, err)
}
return
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
if val.(int64) &lt;= 0 {
err := fmt.Errorf(`%s %w, but it&#39;s not`, name, failures.ShouldBePositive)
*errs = append(*errs, err)
}
return
case float32, float64:
if val.(float64) &lt; 0 {
err := fmt.Errorf(`%s %w, but it&#39;s not`, name, failures.ShouldBePositive)
*errs = append(*errs, err)
}
return
default:
panic(fmt.Sprintf(`%T is not supported type`, val))
}
}

But this approach returns me an error:
test panicked: interface conversion: interface {} is int, not int64

What is a better way to compare that value exceeds zero?

答案1

得分: 1

你的代码在处理"interface conversion: interface {} is int, not int64"时出现问题,因为在多类型的case中,类型切换变量val保持其原始类型。详细信息请参见:https://stackoverflow.com/questions/40575033/golang-multiple-case-in-type-switch。

因此,在这种情况下,你确实需要断言为某种类型,以便使用比较运算符。这个"something"可以是一个类型参数。

    case int, int8, int16, int32, int64:
        if val.(T) <= 0 {
            // ...
        }

但是,这段代码仍然无法使用比较运算符,因为约束Positive包括decimal.Decimal,而decimal.Decimal不支持比较运算。

尝试为decimal.Decimal编写一个case,并为其他数值类型编写另一个case也不会很好,因为你没有一种好的方法来减少类型约束的类型集合。你又回到了为每种类型编写一个case的情况。也许将来Go语言可能会允许在类型切换中使用联合约束。

你现在可以做的是静态地处理decimal.Decimal和其他数值类型。你可以使用constraints包中的类型来避免重新声明所有内容:SignedUnsignedFloat。然后,一个只包含数值类型的简单函数可以这样写:

func StrictlyPositive[T Signed | Unsigned | Float](v T) bool {
    return v > 0
}

但是,对于浮点数,仅使用<是不够的。浮点变量也可能是NaN或+/-无穷大。你必须决定如何排序NaN;无穷大有符号位,但我认为最好使用math.IsInf来不隐藏在比较运算符后面的东西。

因此,总结起来,我认为这个函数最好使用反射,虽然可能会慢一些,但代码并不完全糟糕。以下是你示例的简化版本:

func CheckPositive[T Positive](value T) string {
	switch val := any(value).(type) {
	case decimal.Decimal:
		if val.IsNegative() || val.IsZero() {
			return "non positive decimal"
		}

	case int, int8, int16, int32, int64:
		if reflect.ValueOf(val).Int() <= 0 {
			return "non positive signed"
		}

	case uint, uint8, uint16, uint32, uint64:
		if reflect.ValueOf(val).Uint() == 0 {
			return "non positive unsigned"
		}

	case float32, float64:
		f := reflect.ValueOf(val).Float()
		switch {
		case math.IsNaN(f):
			return "NaN float"
		case math.IsInf(f, -1):
			return "negative infinite"
		case math.IsInf(f, 1):
			// do nothing
		default:
			// not a NaN and not an Infinite
			if f <= 0.0 {
				return "negative float"
			}
		}

	default:
		panic(fmt.Sprintf(`%T is not supported type`, val))
	}
	return "positive"
}
英文:

Your code doesn't work with "interface conversion: interface {} is int, not int64" because in multiple-type case the type switch variable val keeps its original type. See also: https://stackoverflow.com/questions/40575033/golang-multiple-case-in-type-switch for details.

So in this case you have indeed to assert to something in order to use the order operators. That "something" could be a type parameter.

    case int, int8, int16, int32, int64:
if val.(T) &lt;= 0 {
// ...
}

BUT this code still can't use the order operator because the constraint Positive includes decimal.Decimal, which doesn't support ordering.

Attempting to write a case for decimal.Decimal and a case for other numeric types won't work well either, because you haven't a good way to reduce the type set of a type constraint. You are back writing one case for each type. One day Go might allow using union constraints in type switches.

What you can do today is to statically handle decimal.Decimal and other numeric types differently. You can use the types in the package constraints to avoid redeclaring everything: Signed, Unsigned and Float. Then a naive function with only numerical types is as simple as this:

func StrictlyPositive[T Signed | Unsigned | Float](v T) bool {
return v &gt; 0
}

BUT with floats, using &lt; is not enough. Float variables could also be NaN or +/-infinity. You have to decide how to order NaNs; infinities have the sign bit, but IMO it's better to use math.IsInf to not hide stuff behind the order operators.

So in conclusion I think this function is better off with reflection, which might be slower, but the code doesn't totally suck. The following is a simplified version of your example:

func CheckPositive[T Positive](value T) string {
switch val := any(value).(type) {
case decimal.Decimal:
if val.IsNegative() || val.IsZero() {
return &quot;non positive decimal&quot;
}
case int, int8, int16, int32, int64:
if reflect.ValueOf(val).Int() &lt;= 0 {
return &quot;non positive signed&quot;
}
case uint, uint8, uint16, uint32, uint64:
if reflect.ValueOf(val).Uint() == 0 {
return &quot;non positive unsigned&quot;
}
case float32, float64:
f := reflect.ValueOf(val).Float()
switch {
case math.IsNaN(f):
return &quot;NaN float&quot;
case math.IsInf(f, -1):
return &quot;negative infinite&quot;
case math.IsInf(f, 1):
// do nothing
default:
// not a NaN and not an Infinite
if f &lt;= 0.0 {
return &quot;negative float&quot;
}
}
default:
panic(fmt.Sprintf(`%T is not supported type`, val))
}
return &quot;positive&quot;
}

huangapple
  • 本文由 发表于 2023年4月26日 09:06:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76106459.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定