gloang 浮点类型计算,转换和保留精度处理

float类型转换

var f float32 = 3.1415926

// float32 转 float64
fmt.Printf("%v\n", float64(f)) // 输出:3.141592502593994,6位后的小数精度是错误的

// float64 转 float32
var f2 float64 = 3.141592653589793
fmt.Println("%v\n", float32(f2)) // 输出:3.1415927,6位后的小数精度是错误的

现象:
浮点类型在转换的时候会出现结果不精确的现象,出现浮点数不精确的原因是,浮点数储存至内存中时,2的-1、-2….-n次方不能精确的表示小数部分,所以再把这个数从地址中取出来进行计算就出现了偏差.
ps:不是所有的float相加减乘除都一定出现偏差,具体要根据golang实现IEEE 754的情况定

解决思路:

  1. 先将其他类型转为字符串
  2. 通过标准库strconv将改字符串转为对应的浮点类型
// float32Tofloat64 float32转float64
func float32Tofloat64(f float32) float64 {
    str := fmt.Sprintf("%f", f)
    v, err := strconv.ParseFloat(str, 64)
    if err != nil {
        fmt.Println("float32Tofloat64 err:", err, f)
    }
    return v
}

浮点类型转换为其他类型

思路:

  1. 先将浮点类型转换为字符串
  2. 再格式化为具体的类型
// float64ToUint16 float64转uint16
func float64ToUint16(f float64) uint16 {
    s := strconv.FormatFloat(f,'f',-1,64)
    // 第二个参数选项,含义如下:
    // 'b' (-ddddp±ddd,二进制指数)
    // 'e' (-d.dddde±dd,十进制指数)
    // 'E' (-d.ddddE±dd,十进制指数)
    // 'f' (-ddd.dddd,没有指数)
    // 'g' ('e':大指数,'f':其它情况)
    // 'G' ('E':大指数,'f':其它情况)
    i, err := strconv.ParseInt(s, 10, 17)
    // 这里注意 uint16的有效数字位是16, 
    // int16 有效数字位是 15,有一位是数字位
    // 所以要转17位的
    if err != nil {
        fmt.Println("ParseInt err:", err, s)
    }
    return uint16(i)
}

float 类型计算

思路:

  1. float类型转换为精度不丢失的decimal进行计算
  2. 再将decimal转回float类型
import (
    "github.com/shopspring/decimal"
)

// AddFloat decimal类型加法
// return d1 + d2
func AddFloat(d1, d2 float64) float64 {
    decimalD1 := decimal.NewFromFloat(d1)
    decimalD2 := decimal.NewFromFloat(d2)
    decimalResult := decimalD1.Add(decimalD2)
    float64Result, _ := decimalResult.Float64()
    return float64Result
}

// SubtractFloat decimal类型减法
// return d1 - d2
func SubtractFloat(d1, d2 float64) float64 {
    decimalD1 := decimal.NewFromFloat(d1)
    decimalD2 := decimal.NewFromFloat(d2)
    decimalResult := decimalD1.Sub(decimalD2)
    float64Result, _ := decimalResult.Float64()
    return float64Result
}

// MultiplyFloat decimal类型乘法
// return d1 * d2
func MultiplyFloat(d1, d2 float64) float64 {
    decimalD1 := decimal.NewFromFloat(d1)
    decimalD2 := decimal.NewFromFloat(d2)
    decimalResult := decimalD1.Mul(decimalD2)
    float64Result, _ := decimalResult.Float64()
    return float64Result
}

// DivideFloat decimal类型除法
// return d1 / d2
func DivideFloat(d1, d2 float64) float64 {
    decimalD1 := decimal.NewFromFloat(d1)
    decimalD2 := decimal.NewFromFloat(d2)
    decimalResult := decimalD1.Div(decimalD2)
    float64Result, _ := decimalResult.Float64()
    return float64Result
}

float类型保留精度

思路:

  1. 由于直接取整不会四舍五入,故将该值加上 0.5 / 10的n次方(n为精确到小数点后几位)
  2. 再将结果乘10的n次方取整后除于10的n次方
import (
    "math"
    "reflect"
)

// Round 浮点类型保留小数点后n位精度
func Round(f interface{}, n int) (r float64, err error) {
    pow10N := math.Pow10(n)
    switch f.(type) {
    case float32:
        v := reflect.ValueOf(f).Interface().(float32)
        r = math.Trunc((float64(v) + 0.5 / pow10N) * pow10N) / pow10N
    case float64:
        v := reflect.ValueOf(f).Interface().(float64)
        r = math.Trunc((v + 0.5 / pow10N) * pow10N) / pow10N
    }
    return r, err
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注