How can we truncate float64 type to a particular precision?

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

How can we truncate float64 type to a particular precision?

问题

以下是翻译好的代码:

package main

import (
	"fmt"
	"strconv"
)

func main() {
    k := 10/3.0
    i := fmt.Sprintf("%.2f", k)
    f,_ := strconv.ParseFloat(i, 2)
    fmt.Println(f)
}

我不得不编写上面的程序来将一个go的float64变量的精度降低到2位小数。在这种情况下,我同时使用了strconv和fmt。是否有其他逻辑方法可以实现这个目的?

英文:
package main

import (
	"fmt"
	"strconv"
 	)

func main() {
    k := 10/3.0
    i := fmt.Sprintf("%.2f", k)
    f,_ := strconv.ParseFloat(i, 2)
    fmt.Println(f)
}

I had to write the program above to decrease the precision of a go float64 variable to 2.
In this case I was using both strconv and fmt. Is there some other logical method by which it can be done?

答案1

得分: 99

以下代码适用于许多简单的用例,涉及相对较小的数字和小精度输入。然而,由于数字超出float64范围以及IEEE-754舍入误差的问题,它可能无法适用于某些用例(其他语言也存在此问题)。

如果您关心使用更大的数字或需要更高的精度,以下代码可能无法满足您的需求,您应该使用一个辅助库(例如https://github.com/shopspring/decimal)。

我从其他地方找到了一个一行代码的round函数,并且还编写了依赖于round()的toFixed()函数:

func round(num float64) int {
    return int(num + math.Copysign(0.5, num))
}

func toFixed(num float64, precision int) float64 {
    output := math.Pow(10, float64(precision))
    return float64(round(num * output)) / output
}

用法:

fmt.Println(toFixed(1.2345678, 0))  // 1
fmt.Println(toFixed(1.2345678, 1))  // 1.2
fmt.Println(toFixed(1.2345678, 2))  // 1.23
fmt.Println(toFixed(1.2345678, 3))  // 1.235(四舍五入)
英文:

The following code should work for a lot of simple use cases with relatively small numbers and small precision inputs. However, it may not work for some uses cases because of numbers overflowing out of the range of float64 numbers, as well as IEEE-754 rounding errors (other languages have this issue as well).

If you care about using larger numbers or need more precision, the following code may not work for your needs, and you should use a helper library (e.g. https://github.com/shopspring/decimal).

I picked up a one-liner round function from elsewhere, and also made toFixed() which depends on round():

func round(num float64) int {
    return int(num + math.Copysign(0.5, num))
}

func toFixed(num float64, precision int) float64 {
	output := math.Pow(10, float64(precision))
	return float64(round(num * output)) / output
}

Usage:

fmt.Println(toFixed(1.2345678, 0))  // 1
fmt.Println(toFixed(1.2345678, 1))  // 1.2
fmt.Println(toFixed(1.2345678, 2))  // 1.23
fmt.Println(toFixed(1.2345678, 3))  // 1.235 (rounded up)

答案2

得分: 86

你不需要任何额外的代码...它就是这么简单

import (
	"fmt"
)

func main() {
	k := 10 / 3.0
	fmt.Printf("%.2f", k)
}

测试代码

英文:

You don't need any extra code ... its as simple as

import (
	"fmt"
)

func main() {
	k := 10 / 3.0
	fmt.Printf("%.2f", k)
}

Test Code

答案3

得分: 31

我真的看不出来有什么意义,但你可以这样做而不使用strconv:

package main

import (
	"fmt"
)

func main() {
	untruncated := 10 / 3.0
	truncated := float64(int(untruncated * 100)) / 100
	fmt.Println(untruncated, truncated)
}

这段代码的作用是将10除以3,得到一个未截断的浮点数untruncated,然后将untruncated乘以100并转换为整数,再除以100转换回浮点数,得到截断两位小数的浮点数truncated。最后通过fmt.Println打印出untruncated和truncated的值。

英文:

I really don't see the point, but you could do something like this without strconv:

package main

import (
	"fmt"
)

func main() {
	untruncated := 10 / 3.0
	truncated := float64(int(untruncated * 100)) / 100
	fmt.Println(untruncated, truncated)
}

答案4

得分: 17

最简单的解决方案是数值截断(假设i是一个浮点数,你想要保留2位小数):

float64(int(i * 100)) / 100

例如:

i := 123456.789
x := float64(int(i * 100)) / 100
// x = 123456.78

注意!
如果你处理的是大数(可能超过最大值范围),你应该知道上述方法可能会导致严重的浮点数精度问题(参考链接:https://play.golang.org/p/X2tNXihu_uN):

i := float64(1<<63) // 9223372036854775808.0
fmt.Println(i, float64(int64(i * 10)) / 10)

输出结果为:9.223372036854776e+18 -9.223372036854776e+17

另请参考:

  1. 32位浮点数的工作原理
  2. 64位浮点数的工作原理
  3. golang math 数值范围常量
  4. golang math/big
英文:

The simplest solution is numeric truncation (assuming i is a float and you want a precision of 2 decimal points):

float64(int(i * 100)) / 100

For example:

i := 123456.789
x := float64(int(i * 100)) / 100
// x = 123456.78

BEWARE!

If you're dealing with large numbers (numbers that can cross the max value boundaries), you should know that the above can lead to serious floating point accuracy issues:

i := float64(1&lt;&lt;63) // 9223372036854775808.0
fmt.Println(i, float64(int64(i * 10)) / 10)

Prints: 9.223372036854776e+18 -9.223372036854776e+17

See also:

  1. how 32 bit floating point numbers work
  2. how 64 bit floating point numbers work
  3. golang math numeric value range constants
  4. golang math/big

答案5

得分: 13

没有人提到使用math/big。就原始问题而言,结果与被接受的答案相同,但如果你使用需要一定精度(比如货币)的浮点数,那么你应该使用big.Float

根据原始问题:

package main

import (
    "math/big"
    "fmt"
)

func main() {
    // 原始问题
    k := 10 / 3.0
    fmt.Println(big.NewFloat(k).Text('f', 2))
}

不幸的是,你可以看到.Text方法不使用定义的舍入模式(否则这个答案可能更有用),而似乎总是向零舍入:

j := 0.045
fmt.Println(big.NewFloat(j).SetMode(big.AwayFromZero).Text('f', 2))

// 输出 -> 0.04

尽管如此,将浮点数存储为big.Float仍然有一定的优势。

英文:

No one has mentioned using math/big. The results as pertains to the original question are the same as the accepted answer, but if you are working with floats that require a degree of precision ($money$), then you should use big.Float.

Per the original question:

package main

import (
    &quot;math/big&quot;
    &quot;fmt&quot;
)

func main() {
    // original question
    k := 10 / 3.0
    fmt.Println(big.NewFloat(k).Text(&#39;f&#39;, 2))
}

Unfortunately, you can see that .Text does not use the defined rounding mode (otherwise this answer might be more useful), but rather always seems to round toward zero:

j := 0.045
fmt.Println(big.NewFloat(j).SetMode(big.AwayFromZero).Text(&#39;f&#39;, 2)

// out -&gt; 0.04

Nevertheless, there are certain advantages to having your float stored as a big.Float.

答案6

得分: 10

没有问题,以下是代码的中文翻译:

没有检查大浮点数的函数

// 四舍五入,例如 12.3416 -> 12.35
func RoundUp(val float64, precision int) float64 {
    return math.Ceil(val*(math.Pow10(precision))) / math.Pow10(precision)
}

// 向下取整,例如 12.3496 -> 12.34
func RoundDown(val float64, precision int) float64 {
    return math.Floor(val*(math.Pow10(precision))) / math.Pow10(precision)
}

// 四舍五入到最近的数,例如 12.3456 -> 12.35
func Round(val float64, precision int) float64 {
    return math.Round(val*(math.Pow10(precision))) / math.Pow10(precision)
}
英文:

Functions without checking for large floats

// Rounds like 12.3416 -&gt; 12.35
func RoundUp(val float64, precision int) float64 {
	return math.Ceil(val*(math.Pow10(precision))) / math.Pow10(precision)
}

// Rounds like 12.3496 -&gt; 12.34
func RoundDown(val float64, precision int) float64 {
	return math.Floor(val*(math.Pow10(precision))) / math.Pow10(precision)
}

// Rounds to nearest like 12.3456 -&gt; 12.35
func Round(val float64, precision int) float64 {
	return math.Round(val*(math.Pow10(precision))) / math.Pow10(precision)
}

答案7

得分: 8

这个答案 由 threeve 提供,将我带到了 GitHub 上的 这个问题,其中介绍了一种基于 math/big 的解决方案来进行数值舍入 - 这样可以正确使用舍入方法:

package main

import (
	"fmt"
	"math/big"
)

func main() {
	f := new(big.Float).SetMode(big.ToNearestEven).SetFloat64(10/3.0)
	// 使用格式化将结果舍入到2位小数。
	f.SetPrec(8)
	fmt.Printf("%.2f\n", f)
}

在 threeve 的示例中,舍入模式也得到了尊重:

j := 0.045

f := new(big.Float).SetMode(big.AwayFromZero).SetFloat64(j)
// 使用格式化将结果舍入到2位小数。
f.SetPrec(8)
fmt.Printf("%.2f\n", f)

-> 正确输出 0.05

此外,Go 1.10 已经发布,并添加了 math.Round() 函数,可以参考 icza 的优秀答案:https://stackoverflow.com/questions/39544571/golang-round-to-nearest-0-05/39544897#39544897

package main

import (
	"fmt"
	"math"
)

func main() {

	fmt.Println(Round(10/3.0, 0.01))

}

func Round(x, unit float64) float64 {
	return math.Round(x/unit) * unit
}

然而,不应该使用 float 来存储货币值。(参考:https://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency)
解决这个问题的一种方法是使用实现了 decimal 的库,比如 https://github.com/ericlagergren/decimalhttps://github.com/shopspring/decimal

英文:

The answer by threeve brought me to this issue on GitHub where a solution based on math/big for rounding values is presented - this way the rounding method is used correctly:

package main

import (
	&quot;fmt&quot;
	&quot;math/big&quot;
)

func main() {
	f := new(big.Float).SetMode(big.ToNearestEven).SetFloat64(10/3.0)
	// Round to 2 digits using formatting.
	f.SetPrec(8)
	fmt.Printf(&quot;%.2f\n&quot;, f)
}

The rounding mode is also respected in threeve's example:

j := 0.045

f := new(big.Float).SetMode(big.AwayFromZero).SetFloat64(j)
// Round to 2 digits using formatting.
f.SetPrec(8)
fmt.Printf(&quot;%.2f\n&quot;, f)

-&gt; correctly yields 0.05

Also, Go 1.10 has been released and added a math.Round() function, see this excellent answer by icza: https://stackoverflow.com/questions/39544571/golang-round-to-nearest-0-05/39544897#39544897

package main

import (
	&quot;fmt&quot;
	&quot;math&quot;
)

func main() {

	fmt.Println(Round(10/3.0, 0.01))

}

func Round(x, unit float64) float64 {
	return math.Round(x/unit) * unit
}

However, one should not use float for storing monetary values. (See: https://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency)
One way around this is using a library that implements decimal like https://github.com/ericlagergren/decimal or https://github.com/shopspring/decimal

答案8

得分: 3

对于经度和纬度的浮点数,要小心截断和四舍五入,因为这可能会得到完全不同的结果...因为它可能会将您放在错误的纬度边界的一侧。

英文:

Be careful with longitude and latitude float as truncating and rounding can yield completely different results... as it can put you on the wrong side of a latitude boundary.

答案9

得分: 2

这是一个小技巧,可以通过类型转换将浮点数四舍五入:

package main

import (
	"fmt"
)

func main() {
	k := 10 / 3.0
	k = float64(int(k*100)) / 100
	fmt.Println(k)  // 输出 3.33
}

你可以在这个链接上运行这段代码:https://play.golang.org/p/yg2QYcZ-2u

英文:

This is a little workaround how can you round float using type conversion to int back and forth:

package main

import (
    &quot;fmt&quot;
)

func main() {
    k := 10 / 3.0
    k = float64(int(k*100)) / 100
    fmt.Println(k)  // output 3.33
}

https://play.golang.org/p/yg2QYcZ-2u

答案10

得分: 1

func FloatPrecision(num float64, precision int) float64 {
p := math.Pow10(precision)
value := float64(int(num*p)) / p
return value
}

英文:
func FloatPrecision(num float64, precision int) float64 {
	p := math.Pow10(precision)
	value := float64(int(num*p)) / p
	return value
}

答案11

得分: 0

所以在经过多次搜索后,我终于找到了一个非常简单的解决方案,可以让你在没有任何奇怪的数学运算的情况下轻松控制浮点数的精度!

package main

import (
	"fmt"
	"github.com/shopspring/decimal"
)

func main() {
	k := 101.3874927181298723478
	p := 5

	v := decimal.NewFromFloat(k).Round(int32(p))
	fmt.Println(v)
}
// 101.38749

来源:https://godoc.org/github.com/shopspring/decimal#Decimal.Round

虽然我喜欢一些简单的方法,比如 &quot;%.3f\n&quot;, k,但这些方法会产生一个字符串,然后我还需要用另一个 strconv 命令将其转换回浮点数,而我不想这样做。

英文:

So after much hunting around, coming to this thread multiple times I finally found a very simple solution that allows you to control the precision of floats very simply without any weird math!

package main

import (
	&quot;fmt&quot;
	&quot;github.com/shopspring/decimal&quot;
)

func main() {
	k := 101.3874927181298723478
	p := 5

	v := decimal.NewFromFloat(k).Round(int32(p))
	fmt.Println(v)
}
// 101.38749

Source: https://godoc.org/github.com/shopspring/decimal#Decimal.Round

While I like some of the simple methods like &quot;%.3f\n&quot;, k options, these produced a string that I would then have to convert back into a float with another strconv command that I didn't want to do.

答案12

得分: 0

将数字保留到小数点后一位:

fmt.Printf("%.1f", n)

<https://go.dev/play/p/eavHNPXhZK6>

英文:

Round to the 1st decimal

fmt.Printf(&quot;%.1f&quot;, n)

<https://go.dev/play/p/eavHNPXhZK6>

答案13

得分: 0

为了避免舍入误差和溢出,我们可以进行以下操作:

  1. 首先,使用fmt.Sprintffloat64转换为string
  2. 然后,使用strconv.ParseFloat将字符串转换为浮点数。
func adjustDecimals(num float64, precision int) (float64, error) {
    formatString := fmt.Sprintf("%%.%df", precision)
    strNum := fmt.Sprintf(formatString, num)
    return strconv.ParseFloat(strNum, 64)
}

你可以在以下链接中查看代码示例:https://go.dev/play/p/cafkX_3Asw5

英文:

To avoid rounding issues and overflows, we may do -

  1. First, convert float64 to string using fmt.Sprintf
  2. Then, use strconv.ParseFloat to convert the string to float
func adjustDecimals(num float64, precision int) (float64, error) {  
	formatString := fmt.Sprintf(&quot;%%.%df&quot;, precision)  
    strNum := fmt.Sprintf(formatString, num)  
    return strconv.ParseFloat(strNum, 64)  
}  

https://go.dev/play/p/cafkX_3Asw5

答案14

得分: -4

修改自@creack

package main

import (
    "fmt"
)

func main() {

    //未截断的值 := 10/3.0
    未截断的值 := 4.565
    临时值 := int(未截断的值*100)
    小数点后一位 := int(未截断的值*1000)-临时值*10
    if 小数点后一位>=5{
	    临时值 += 1
    }
    截断后的值 := float64(临时值)/100

    fmt.Println(未截断的值, 截断后的值)
}
英文:

modify from @creack

package main

import (
    &quot;fmt&quot;
)

func main() {

    //untruncated := 10/3.0
    untruncated := 4.565
    tmp := int(untruncated*100)
    last := int(untruncated*1000)-tmp*10
    if last&gt;=5{
	    tmp += 1
    }
    truncated := float64(tmp)/100

    fmt.Println(untruncated, truncated)
}

huangapple
  • 本文由 发表于 2013年8月23日 04:29:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/18390266.html
匿名

发表评论

匿名网友

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

确定