首页 > 其他分享 >涉及到金额的时候是,使用Decimal而不是float和double 浮点数

涉及到金额的时候是,使用Decimal而不是float和double 浮点数

时间:2023-12-22 22:57:56浏览次数:35  
标签:decimal 二进制 double 浮点数 .... 十进制 0.1 Decimal 小数

decimal库包是用来解决float类型对象之间运算不准确的问题的。
所以,如果你想使用decimal库包,你必须先把float类型对象通过decimal.NewFromFloat()函数转成decimal.Decimal类型,然后再计算,最后还得再转成你所需要的类型。

范例:

复制代码
package main

import "log"

func main() {
    a := 1129.6
    log.Println(a * 100) //输出:112959.99999999999

    var b float64 = 1129.6
    log.Println(b * 100) //输出:112959.99999999999

    m1 := 8.2
    m2 := 3.8
    log.Println(m1 - m2) // 期望是4.4,结果打印出了4.399999999999999
}

// 输出的结果是:
2023/03/16 13:38:24 112959.99999999999
2023/03/16 13:38:24 112959.99999999999
2023/03/16 13:38:24 4.3999999999999995
// 从上面的输出结果看到,数值的精度出现了一定程度的变化,这显然不是我们想要的结果。
复制代码

 

解决:

由于golang中默认是没有decimal类型的,所以要解决上述的问题,需要使用第三方库包decimal decimal.Decimal是一种数据类型

go get github.com/shopspring/decimal

 

使用:

复制代码
package main

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

func main() {
    var v1 = decimal.NewFromFloat(0.1)  // 声明一个decimal.Decimal类型的变量v1
    var v2 = decimal.NewFromFloat(0.2)  // 声明一个decimal.Decimal类型的变量v2

// 支持从其他数据类型中解析浮点型数据 NewFromString / NewFromFloat32 / NewFromInt() ... // decimal.Decimal类型变量之间的加减乘除 // 所得到的结果也是decimal.Decimal类型 var v3 = v1.Add(v2) // 0.3 var v4 = v1.Sub(v2) // -0.1 var v5 = v1.Mul(v2) // 0.02 var v6 = v1.Div(v2) // 0.5 // 声明一个decimal.Decimal类型的对象 var v7 = decimal.NewFromFloat(3.4625) var data1 = v7.Round(1) // 3.5,保留一位小数,四舍五入的方式 var data2 = v7.Truncate(1) // 3.4,保留一位小数,直接舍弃,直接截断的方式 log.Println(v3, v4, v5, v6) log.Println(data1, data2) // 输出的结果是 0.3 -0.1 0.02 0.5 3.5 3.4 // 最后别忘了还需要转换成你所需要的数据类型 decimal.NewFromFloat(nums).Round(2).Float64() // 四舍五入保留2位小数,最后再转换为float64类型

// 转换成其他数据类型
Float64() // 浮点型
String() // 字符串
  IntPart()  // 返回整数部分   
}
复制代码

项目实战: 

复制代码
// Fen2Yuan 分转元
func Fen2Yuan[K int | uint](price K) (res float64) {
    d := decimal.New(1, 2)
    res, _ = decimal.NewFromInt(int64(float64(price))).DivRound(d, 2).Float64()
    return
}

// Yuan2Fen 元转分
func Yuan2Fen(price float32) (yuan int64) {
    p := decimal.NewFromFloat32(price)
    yuan = p.Mul(decimal.NewFromInt(100)).IntPart()
    return
}
复制代码

 

浮点精度(float、double)运算不精确的原因

最新推荐文章于 2023-10-28 14:28:42 发布    

为什么浮点精度运算会有问题

我们平常使用的编程语言大多都有一个问题——浮点型精度运算会不准确。比如

笔者在测试的时候发现 C/C++ 竟然不会出现这种问题,我最初以为是编译器优化,把这个问题解决了。但是 C/C++ 如果能解决其他语言为什么不跟进?根据这个问题的产生原因来看,编译器优化解决这个问题逻辑不通。后来发现是打印的方法有问题,打印输出方法会四舍五入。使用 printf("%0.17f\n", num); 以及 cout << setprecision(17) << num2 << endl; 多打印几位小数即可看到精度运算不准确的问题。

double num = 0.1 + 0.1 + 0.1;
// 输出结果为 0.30000000000000004
double num2 = 0.65 - 0.6;
// 输出结果为 0.05000000000000004

 

那么精度运算不准确这是为什么呢?我们接下来就需要从计算机所有数据的表现形式二进制说起了。如果大家很了解二进制与十进制的相互转换,那么就能轻易的知道精度运算不准确的问题原因是什么了。如果不知道就让我们一起回顾一下十进制与二进制的相互转换流程。一般情况下二进制转为十进制我们所使用的是按权相加法。十进制转二进制是除2取余,逆序排列法。很熟的同学可以略过。

 

// 二进制到十进制
10010 = 0 * 2^0 + 1 * 2^1 + 0 * 2^2 + 0 * 2^3 + 1 * 2^4 = 18  
 
// 十进制到二进制
18 / 2 = 9 .... 0 
9 / 2 = 4 .... 1 
4 / 2 = 2 .... 0 
2 / 2 = 1 .... 0 
1 / 2 = 0 .... 1
 
10010

 

那么,问题来了十进制小数和二进制小数是如何相互转换的呢?十进制小数到二进制小数一般是整数部分除 2 取余,逆序排列小数部分使用乘 2 取整数位,顺序排列。二进制小数到十进制小数还是使用按权相加法

// 二进制到十进制
10.01 = 1 * 2^-2 + 0 * 2^-1 + 0 * 2^0 + 1 * 2^1 = 2.25
 
// 十进制到二进制
// 整数部分
2 / 2 = 1 .... 0
1 / 2 = 0 .... 1
// 小数部分
0.25 * 2 = 0.5 .... 0 
0.5 * 2 = 1 .... 1 
 
// 结果 10.01

(

十进制小数转换成二进制小数采用"乘2取整,顺序排列"法。以0.875为例,具体做法是:

一、取整运算

1、用2乘十进制小数,可以得到积:2*0.875=1.75;

2、将积的整数部分1取出,再用2乘余下的小数部分0.75,又得到一个积,则2*0.75=1.5

3、再将积的整数部分取出,如此进行,则0.5*2=1.0;此时,积中的小数部分为零,此时0或1为二进制的最后一位,不再往下计算。

二、按序排列

把取出的整数部分按顺序排列起来,先取的整数作为二进制小数的高位有效位,后取的整数作为低位有效位。即0.875=(0.111)B

十进制小数转二进制

如:0.625=(0.101)B

0.625*2=1.25======取出整数部分1

0.25*2=0.5========取出整数部分0

0.5*2=1==========取出整数部分1

再如:0.7=(0.1 0110 0110...)B

0.7*2=1.4========取出整数部分1

0.4*2=0.8========取出整数部分0

 

小数部分乘2取整法的说明:

学习二进制,最好的方法就是类比。

考虑一个十进制小数0.123,我们可以用“乘10取整”法得到它的每一位小数:第一位小数是0.123*10=1.23,取整数1;第二位小数:0.23*10=2.3,取整数2……

上面的方法供你直观理解,下面我从数学的角度分析其中的原理。

现在有一个十进制小数为0.625,要把它转换为二进制小数,我们需要找到它的每一位。记这个二进制小数点后第1位是a1,第二位是a2,……,那么这个小数的值就是a1*(1/2)^(-1)+a2*(1/2)^(-2)+a3*(1/2)^(-3)+…。现在我们的目标是根据0.625找到对应的a1,a2,a3,…使得0.625=a1*(1/2)^(-1)+a2*(1/2)^(-2)+a3*(1/2)^(-3)+…

在等式两边同时乘以2,得到1.25=a1*(1/2)^(0)+a2*(1/2)^(-1)+a3*(1/2)^(-2)+…

我们发现,左边的整数部分1对应右边的a1,也就是二进制小数的第一位,于是a1=1,对于剩下的部分:

0.25=a2*(1/2)^(-1)+a3*(1/2)^(-2)+…

我们再次乘以2,得到0.5=a2*(1/2)^(0)+a3*(1/2)^(-1)+… 于是a2=0

再乘以2,得到1=a3*(1/2)^(0)+…, 于是a3=1,到这里,所有的数都消耗完了,我们找到了0.625对应的二进制小数:0.101

 

版权声明:本文为转载文章,遵循 CC 4.0 BY-SA 版权协议,附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_42552410/article/details/113020126

0.75 = 1/2 + 1/4 + 0/8 + 0/16 + ... ;

你左右都乘2 , 变成了1.5 = 1 + 1/2 + 0/4 + 0/8 + ...;

会发现二分位(即二进制小数第一位)变成了整数, 四分位变成了二分位, 也就是整体往前进了一位.

而这个进到整数位的"1"就是之前的二分位. 再乘2, 最一开始的四分位就会从二分位进到整数位.

二分位的含义可以参考"十分位", "百分位", "千分位"



 链接:https://www.zhihu.com/question/426214209/answer/1530464201
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

 

转小数我们也了解了,接下来我们回归正题,为什么浮点运算会有精度不准确的问题。接下来我们看一个简单的例子 2.1 这个十进制数转成二进制是什么样子的。

2.1 分成两部分
// 整数部分
2 / 2 = 1 .... 0
1 / 2 = 0 .... 1
 
// 小数部分
0.1 * 2 = 0.2 .... 0
0.2 * 2 = 0.4 .... 0
0.4 * 2 = 0.8 .... 0
0.8 * 2 = 1.6 .... 1
0.6 * 2 = 1.2 .... 1
0.2 * 2 = 0.4 .... 0
0.4 * 2 = 0.8 .... 0
0.8 * 2 = 1.6 .... 1
0.6 * 2 = 1.2 .... 1
0.2 * 2 = 0.4 .... 0
0.4 * 2 = 0.8 .... 0
0.8 * 2 = 1.6 .... 1
0.6 * 2 = 1.2 .... 1
............

 

落入无限循环结果为 10.0001100110011........ , 我们的计算机在存储小数时肯定是有长度限制的,所以会进行截取部分小数进行存储,从而导致计算机存储的数值只能是个大概的值,而不是精确的值。从这里看出来我们的计算机根本就无法使用二进制来精确的表示 2.1 这个十进制数字的值,连表示都无法精确表示出来,计算肯定是会出现问题的。

精度运算丢失的解决办法

现有有三种办法

  1. 如果业务不是必须非常精确的要求可以采取四舍五入的方法来忽略这个问题。
  2. 转成整型再进行计算。
  3. 使用 BCD 码存储和运算二进制小数(感兴趣的同学可自行搜索学习)。

一般每种语言都用高精度运算的解决方法(比一般运算耗费性能),比如 Python 的 decimal 模块,Java 的 BigDecimal,但是一定要把小数转成字符串传入构造,不然还是有坑,其他语言大家可以自行寻找一下。

# Python 示例
from decimal import Decimal
 
num = Decimal('0.1') + Decimal('0.1') + Decimal('0.1')
print(num)
// Java 示例
import java.math.BigDecimal;
 
BigDecimal add = new BigDecimal("0.1").add(new BigDecimal("0.1")).add(new BigDecimal("0.1"));
System.out.println(add);

 

拓展:详解浮点型

上面既然提到了浮点型的存储是有限制,那么我们看一下我们的计算机是如何存储浮点型的,是不是真的正如我们上面提到的有小数长度的限制。
那我们就以 Float 的数据存储结构来说,根据 IEEE 标准浮点型分为符号位,指数位和尾数位三部分(各部分大小详情见下图)。

IEEE 754 标准

一般情况下我们表示一个很大或很小的数通常使用科学记数法,例如:1000.00001 我们一般表示为 1.00000001 * 10^3,或者 0.0001001 一般表示为 1.001 * 10^-4。

符号位

0 是正数,1 是负数

指数位

指数很有意思因为它需要表示正负,所以人们创造了一个叫 EXCESS 的系统。这个系统是什么意思呢?它规定 最大值 / 2 - 1 表示指数为 0。我们使用单精度浮点型举个例子,单精度浮点型指数位一共有八位,表示的十进制数最大就是 255。那么 255 / 2 - 1 = 127,127 就代表指数为 0。如果指数位存储的十进制数据为 128 那么指数就是 128 - 127 = 1,如果存储的为 126,那么指数就是 126 - 127 = -1。

尾数位

比如上述例子中 1.00000001 以及 1.001 就属于尾数,但是为什么叫尾数呢?因为在二进制中比如 1.xx 这个小数,小数点前面的 1 是永远存在的,存了也是浪费空间不如多存一位小数,所以尾数位只会存储小数部分。也就是上述例子中的 00000001 以及 001 存储这样的数据。

IEEE 754 标准

通过上述程序我们得到的存储 1.25 的 float 二进制结构的具体值为 00111111101000000000000000000000 ,我们拆分一下 0 为符号位他是个正值。01111111 为指数位,01000000000000000000000 是尾数。接下来我们验证一下 01111111 转为十进制是 127,那么经过计算指数为 0。尾数是 01000000000000000000000 加上默认省略的 1 为 1.01(省略后面多余的 0),转换为十进制小数就是 1.25。

相似资料:https://cloud.tencent.com/developer/article/1473541

参考:

https://www.cnblogs.com/xingxia/p/golang_decimal.html

https://www.jianshu.com/p/0c2c5ead2357

 

标签:decimal,二进制,double,浮点数,....,十进制,0.1,Decimal,小数
From: https://www.cnblogs.com/youxin/p/17922499.html

相关文章

  • python 浮点数 round 舍一法 向零取整 df 数组 Series 三种数据类型实现
    介绍:python的round函数,默认进行四舍五入,我需要将3.45保留一位小数,3.4 一、一般格式使用Python的内置函数 math.floor() 来向下取整到指定的小数位数。例如,如果你想保留小数点后一位并向下取整,可以这样做:importmathnum=3.45rounded_num=math.floor(num*10)/......
  • decimal插件计算
    import{Decimal}from'decimal.js';//引入exportconstcal={jia(num1,num2){returnnewDecimal(num1).add(newDecimal(num2))},jian(num1,num2){returnnewDecimal(num1).sub(newDecimal(num2))},cheng(num1,num2){returnnewDecimal(num......
  • Python中isdigit、isnumeric、isdecimal
    isdigit字符串的isdigit方法用于判断字符串是否只包含数字,即0-9的字符print('1233'.isdigit())#Trueprint('12.33'.isdigit())#Falseisnumeric字符串的isnumeric方法可用于判断字符串是否是数字,数字包括Unicode数字,byte数字(单字节),全角数字(双字节),罗马数字print('23......
  • 浮点数与定点数相互转换(仅考虑正数情况)
    1.浮点数转换为定点数定点数总位宽设为\(W\),小数部分字长设为\(F\)那么,考虑一个浮点数\(a\),将其转换为定点数的操作为:一:计算\(b=a\times2^{F}\);二:将\(b\)化为整数\(^*\);三:用二进制将\(b\)表示成\(c\);四:用\(N\)位二进制数表示\(c\)成\(......
  • 关于浮点数误差以及四舍五入
    https://blog.csdn.net/Xavier_97/article/details/126931927由于很玄学,我们考虑统一使用库函数round和自己手写round来实现最终输出整数的四舍五入和小数保留k位的四舍五入#include<iostream>#include<cmath>usingnamespacestd;intmain(){doublea=1.4999999......
  • BigDecimal处理Double类型精度问题
    最近发现工程里出现金额精度问题,记录下使用区别:publicstaticvoidmain(String[]args){Doublea=0.1;BigDecimalb=newBigDecimal(a);BigDecimalc=BigDecimal.valueOf(a);System.out.println("bis:"+b);System.out......
  • 【C语言基础】float、double 浮点数类型的四舍五入问题
    简短不看版:C语言中,不能进行doublea==doubleb 这样的运算。另外,printf(".1f",&double)的时候,数据输出不同时候,可能会不一样。根本问题时float类型或者double类型的浮点数在计算机中不能精确储存。              单精度浮点型(float)存储方式比如,若我们希......
  • bigdecimal保留两位小数
    1 publicclasstest1_format{2 publicstaticvoidmain(String[]args){3 BigDecimaldecimal=newBigDecimal("1.12345");4 System.out.println(decimal);5 BigDecimalsetScale=decimal.setScale(4,BigDecimal.ROUND_HALF_DOWN);6 Syste......
  • 【C语言】浮点数在内存中的存储
    常⻅的浮点数:3.14159、1E10等,浮点数家族包括:float、double、longdouble类型。 浮点数表⽰的范围:float.h中定义我们先通过一道题目来了解:#include<stdio.h>intmain(){intn=9;float*pFloat=(float*)&n;printf("n的值为:%d\n",n);printf("*pFloat的值为:%f\n",......
  • Java之API详解之BigDecimal类的详细解析
     7BigDecimal类7.1引入首先我们来分析一下如下程序的执行结果:publicclassBigDecimalDemo01{publicstaticvoidmain(String[]args){System.out.println(0.09+0.01);}}这段代码比较简单,就是计算0.09和0.01之和,并且将其结果在控制台进行输出。那么......