如何在将float64转换为float32时检查精度损失

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

Go: How to check precision loss when converting float64 to float32

问题

我有一个场景,我接收到一个float64的值,但必须将其作为float32的值发送到另一个服务。我们知道接收到的值应该总是适合float32的。然而,为了安全起见,我想记录一下我们通过转换为float32而丢失数据的情况。

这段代码块无法编译,因为你不能直接比较float32和float64。

func convert(input float64) (output float32, err error) {
	const tolerance = 0.001
	output = float32(input)
	if output > input+tolerance || output < input-tolerance {
		return 0, errors.New("lost too much precision")
	}
	return output, nil
}

有没有一种简单的方法来检查我是否满足了这个条件?这个检查会频繁发生,所以我想避免进行字符串转换。

英文:

I have a scenario where I receive a float64 value, but must send it down the wire to another service as a float32 value. We know the received value should always fit into a float32. However, to be safe I want to log the case where we are losing data by converting to float32.

This code block does not compile, since you can't compare float32 to float64 directly.

func convert(input float64) (output float32, err error) {
	const tolerance = 0.001
	output = float32(input)
	if output &gt; input+tolerance || output &lt; input-tolerance {
		return 0, errors.New(&quot;lost too much precision&quot;)
	}
	return output, nil
}

Is there an easy way to check that I am hitting this condition? This check will happen at high frequency, so I want to avoid doing string conversions.

答案1

得分: 4

你可以将float32的值转换回float64,只是为了验证。

要检查转换后的值是否表示相同的值,只需将其与原始值(输入)进行比较。只返回一个ok bool信息(而不是一个error)也足够/符合惯例:

func convert(input float64) (output float32, ok bool) {
    output = float32(input)
    ok = float64(output) == input
    return
}

(注意:未检查NaN等边缘情况。)

进行测试:

fmt.Println(convert(1))
fmt.Println(convert(1.5))
fmt.Println(convert(0.123456789))
fmt.Println(convert(math.MaxFloat32))

输出结果(在Go Playground上尝试):

1 true
1.5 true
0.12345679 false
3.4028235e+38 true

请注意,由于float32的精度小于float64,因此这通常会导致ok = false的结果,即使转换后的值可能非常接近输入值。

因此,在实践中,检查转换值的差异会更有用。您提出的解决方案检查了绝对差值,这并不是很有用:例如,1000000.11000000是非常接近的数字,尽管差值为0.10.00010.00011的差异要小得多:0.00001,但与这些数字相比,差异要大得多。

因此,您应该检查相对差异,例如:

func convert(input float64) (output float32, ok bool) {
    const maxRelDiff = 1e-8

    output = float32(input)
    diff := math.Abs(float64(output) - input)
    ok = diff <= math.Abs(input)*maxRelDiff

    return
}

进行测试:

fmt.Println(convert(1))
fmt.Println(convert(1.5))
fmt.Println(convert(1e20))
fmt.Println(convert(math.Pi))
fmt.Println(convert(0.123456789))
fmt.Println(convert(math.MaxFloat32))

输出结果(在Go Playground上尝试):

1 true
1.5 true
1e+20 false
3.1415927 false
0.12345679 false
3.4028235e+38 true
英文:

You can convert back the float32 value to float64, just for the validation.

To check if the converted value represents the same value, simply compare it to the original value (the input). It's also enough / idiomatic to just return an ok bool info (instead of an error):

func convert(input float64) (output float32, ok bool) {
	output = float32(input)
	ok = float64(output) == input
	return
}

(Note: edge cases like NaN are not checked.)

Testing it:

fmt.Println(convert(1))
fmt.Println(convert(1.5))
fmt.Println(convert(0.123456789))
fmt.Println(convert(math.MaxFloat32))

Output (try it on the Go Playground):

1 true
1.5 true
0.12345679 false
3.4028235e+38 true

Note that this will often give ok = false result because the precision of float32 is less than that of float64, even though the converted value may be very close to the input.

So in practice it would be more useful to check the difference of the converted value. Your proposed solution checks for the absolute difference value which is not so useful: for example 1000000.1 and 1000000 are very close numbers, even though the difference is 0.1. 0.0001 and 0.00011 have much less difference: 0.00001, yet the difference compared to the numbers is much bigger.

So you should check the relative difference, for example:

func convert(input float64) (output float32, ok bool) {
	const maxRelDiff = 1e-8

	output = float32(input)
	diff := math.Abs(float64(output) - input)
	ok = diff &lt;= math.Abs(input)*maxRelDiff

	return
}

Testing it:

fmt.Println(convert(1))
fmt.Println(convert(1.5))
fmt.Println(convert(1e20))
fmt.Println(convert(math.Pi))
fmt.Println(convert(0.123456789))
fmt.Println(convert(math.MaxFloat32))

Output (try it on the Go Playground):

1 true
1.5 true
1e+20 false
3.1415927 false
0.12345679 false
3.4028235e+38 true

答案2

得分: 0

是的。检查该值是否不超过上限或下限值。然后确保52-23位的最低有效位为0。(简而言之)

英文:

Yes. Check that the value does not exceed the upper or lower value limit. Then ensure the 52 - 23 least significant bits are 0. (in a nutshell)

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

发表评论

匿名网友

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

确定