Golang中strconv.FormatFloat导致精度丢失问题

Golang中strconv.FormatFloat导致精度丢失问题 根据发布的代码:

	fmt.Println(strconv.FormatFloat(220.355, 'g', 30, 64))
	fmt.Println(strconv.FormatFloat(0.001, 'g', 30, 64))

输出结果为:

220.354999999999989768184605055
0.00100000000000000002081668171172

需要注意的是原始数字’220.355’,如果我想恢复这个数字,必须使用精度13进行四舍五入。 我原本期望float64至少实现了15位精度!!!所以直到今天我一直使用round(15)来恢复我的实际数字。15位刚好满足我的需求,但13位…当然不行!

所以如果我想达到满足要求的精度,我是否必须使用字符串或者(大整数+指数)的方式?


更多关于Golang中strconv.FormatFloat导致精度丢失问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

你可以随意进行四舍五入,但你尝试的这两个数值都无法被精确表示。如果它们能够被精确表示,你应该会在最后给出的数字后面只看到0

如果你需要任何形式的精度,请避免使用浮点数。建议尝试使用定点数类型。

// 代码示例保留原样

更多关于Golang中strconv.FormatFloat导致精度丢失问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这是一个典型的浮点数精度问题,不是strconv.FormatFloat函数的问题。浮点数在计算机中是以二进制形式存储的,很多十进制小数无法精确表示为二进制浮点数。

你观察到的现象是因为220.355这个十进制数在转换为float64时已经存在精度损失。让我们通过代码来验证:

package main

import (
	"fmt"
	"math"
	"strconv"
)

func main() {
	// 原始问题展示
	fmt.Println("原始输出:")
	fmt.Println(strconv.FormatFloat(220.355, 'g', 30, 64))
	fmt.Println(strconv.FormatFloat(0.001, 'g', 30, 64))
	
	// 验证220.355在二进制表示中的实际值
	fmt.Println("\n验证220.355的实际存储值:")
	f := 220.355
	fmt.Printf("直接打印: %v\n", f)
	fmt.Printf("30位精度: %.30f\n", f)
	
	// 检查是否等于期望值
	fmt.Printf("等于220.355? %v\n", f == 220.355)
	
	// 使用math.Nextafter查看相邻的浮点数
	lower := math.Nextafter(f, f-1)
	higher := math.Nextafter(f, f+1)
	fmt.Printf("相邻浮点数:\n")
	fmt.Printf("前一个: %.30f\n", lower)
	fmt.Printf("当前:   %.30f\n", f)
	fmt.Printf("后一个: %.30f\n", higher)
	
	// 使用不同的格式化选项
	fmt.Println("\n不同格式化选项:")
	fmt.Printf("'f'格式: %s\n", strconv.FormatFloat(220.355, 'f', -1, 64))
	fmt.Printf("'e'格式: %s\n", strconv.FormatFloat(220.355, 'e', -1, 64))
	fmt.Printf("'g'格式: %s\n", strconv.FormatFloat(220.355, 'g', -1, 64))
}

输出结果会显示220.355在float64中的实际存储值确实是220.354999999999989768184605055。

对于需要精确十进制运算的场景,你有几个选择:

  1. 使用math/big包进行精确计算
package main

import (
	"fmt"
	"math/big"
)

func main() {
	// 使用big.Float进行精确计算
	f := big.NewFloat(220.355)
	f.SetPrec(100) // 设置高精度
	fmt.Printf("big.Float: %s\n", f.Text('f', 30))
	
	// 使用big.Rat进行有理数运算
	r := big.NewRat(220355, 1000) // 220.355 = 220355/1000
	fmt.Printf("big.Rat: %s\n", r.FloatString(30))
}
  1. 使用定点数表示
package main

import (
	"fmt"
)

// 使用整数表示定点数
func main() {
	// 以分为单位存储金额
	amountInCents := 22035 // 220.35元
	fmt.Printf("金额: %d.%02d\n", amountInCents/100, amountInCents%100)
	
	// 或者使用自定义的定点数类型
	type FixedPoint int64
	const scale = 1000000 // 6位小数精度
	
	var value FixedPoint = 220355000 // 220.355
	fmt.Printf("定点数: %d.%06d\n", value/scale, value%scale)
}
  1. 使用decimal第三方库
// 需要导入: go get github.com/shopspring/decimal
package main

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

func main() {
	d := decimal.NewFromFloat(220.355)
	fmt.Printf("decimal: %s\n", d.String())
	
	// 精确运算
	result := d.Add(decimal.NewFromFloat(0.001))
	fmt.Printf("加法结果: %s\n", result.String())
}

对于金融计算或其他需要精确十进制运算的场景,推荐使用math/big或专门的decimal库,而不是直接使用float64。浮点数只适用于可以容忍微小精度误差的科学计算和图形处理等场景。

回到顶部