Java中使用long类型实现精确的四则运算
引子
Effective Java 2nd Edition 第48条建议:如果需要精确的答案,请避免使用float和doble。float和double类型主要是为了科学计算和工程计算而设计的。他们执行二进制制浮点运算(binary floating-point arithmetic),这是为了在广泛的数值范围上提供较为精确的快速近似计算而精心设计的。然而,他们并没有提供完全精确的结果,所以不应该被用于需要精确结果的场合。float和double类型尤其不适合用于货币计算,因为要让float或double精确的表示0.1(或者10的任何其他负数次方值)是不可能的。解决这个问题的正确办法是使用BigDecimal、int或long进行货币计算。
1. float和double为何是近似计算
float和double无法精确计算是由他们的设计决定的。详细信息请参考下面这篇博文:
java float double精度为什么会丢失?浅谈java的浮点数精度问题
2. 使用long实现精确计算
本例子实现了使用long类型来完成浮点数的精确的加减乘,由于除法本身很多是不能整除的,所以对除法的精确计算没有想到好的方法。
由于long的表示范围为 -2^63 —– 2^63 - 1,即十进制的: -9223372036854775808 —–9223372036854775807
所以对于使用long完成精确计算必须保证计算结果及其中间结果不超过long的范围。对于比较大的数,使用BigDecimal靠谱。
上代码:
package effectivejava.chapter8;
import java.math.BigDecimal;
/**
*
* 本例子实现了使用long类型来完成浮点数的精确的加减乘,由于除法本身很多是不能整除的,所以对除法的精确计算没有想到好的方法。
* 由于long的表示范围为-2^63 ----- 2^63 - 1,即十进制的: -9223372036854775808-----9223372036854775807
* 所以对于使用long完成精确计算必须保证计算结果及其中间结果不超过long的范围。对于比较大的数,使用BigDecimal靠谱。
*/
public class ExactComputationByLong {
/**
* 1. 获取两个小数的右移小数点之后的long值及移动位数; 2. 统一移动位数为两个之中大的那个; 3. 相加; 4. 将小数点左移到相加的数中。
*/
public static String add(String strA, String strB) {
ExactLong exactA = new ExactLong(strA);
ExactLong exactB = new ExactLong(strB);
int maxScale = getMaxScale(exactA, exactB);
changeSacle(exactA, maxScale);
changeSacle(exactB, maxScale);
long addNum = exactA.getData() + exactB.getData();
String resultInit = getDataFromLong(addNum, maxScale);
return getResultWithZero(resultInit);
}
/**
* 过程与加法相同。
*/
public static String subtract(String strA, String strB) {
ExactLong exactA = new ExactLong(strA);
ExactLong exactB = new ExactLong(strB);
int maxScale = getMaxScale(exactA, exactB);
changeSacle(exactA, maxScale);
changeSacle(exactB, maxScale);
long subNum = exactA.getData() - exactB.getData();
String resultInit = getDataFromLong(subNum, maxScale);
return getResultWithZero(resultInit);
}
/**
* 过程与加法相似,唯一不同是移动位数为maxScale * 2。
*/
public static String multiply(String strA, String strB) {
ExactLong exactA = new ExactLong(strA);
ExactLong exactB = new ExactLong(strB);
if (exactA.getData() == 0 || exactB.getData() == 0) {
return "0";
}
int maxScale = getMaxScale(exactA, exactB);
changeSacle(exactA, maxScale);
changeSacle(exactB, maxScale);
long subNum = exactA.getData() * exactB.getData();
String resultInit = getDataFromLong(subNum, maxScale * 2);
return getResultWithZero(resultInit);
}
/**
* 对于除法,我没有想到好的方法来精确计算。而且除法本身有很多不是整除的,所以除法一般都要确定一个精度,然后再计算。水平有限,这里就用BigDecimal的一个实现代替吧☺
*/
public static String divide(String strA, String strB) {
return new BigDecimal(strA).divide(new BigDecimal(strB), BigDecimal.ROUND_DOWN).toPlainString();
}
private static void changeSacle(ExactLong exactData, int maxScale) {
if (maxScale != exactData.getScale()) {
exactData.setData((long) (exactData.getData() * Math.pow(10, maxScale - exactData.getScale())));
exactData.setScale(maxScale);
}
}
private static int getMaxScale(ExactLong exactA, ExactLong exactB) {
int maxScale = exactA.getScale() > exactB.getScale()?exactA.getScale():exactB.getScale();
return maxScale;
}
/**
* 将dataL小数点左移scale位
*/
private static String getDataFromLong(long dataL, int scale) {
String dataStr = dataL + "";
if (scale < 0) {
throw new IllegalArgumentException("The degree must greater than 0!!");
}
if (scale == 0) {
return dataStr;
}
if (dataStr.length() > scale) {
return dataStr.substring(0, dataStr.length() - scale) + "." + dataStr.substring(dataStr.length() - scale);
} else {
StringBuilder dataSB = new StringBuilder("0.");
for (int i = 0;i < scale - dataStr.length();i++) {
dataSB.append("0");
}
dataSB.append(dataStr);
return dataSB.toString();
}
}
/**
* 去掉小数点之后多余的0
*/
private static String getResultWithZero(String resultInit) {
int index = resultInit.length();
// 去处小数位最后可能的多个0
for (int i = resultInit.length() - 1;i >= 0;i--) {
if (resultInit.charAt(i) != '0') {
index = i + 1;
break;
}
}
return resultInit.substring(0, index);
}
public static void main(String[] args) {
String strA = "0.001234560";
String strB = "10.2345";
String strAdd = add(strA, strB);
System.out.println("Add:\n" + strA + "\n" + strB + "\n" + strAdd);
String strSub = subtract(strA, strB);
System.out.println("Sub:\n" + strA + "\n" + strB + "\n" + strSub);
String strMult = multiply(strA, strB);
System.out.println("Mult:\n" + strA + "\n" + strB + "\n" + strMult);
String sttDiv = divide(strA, strB);
System.out.println("Div:\n" + strA + "\n" + strB + "\n" + sttDiv);
}
}
/**
* 将一个字符串表示的浮点数用long表示,scale表示小数点右移的位数。
*/
class ExactLong {
private long data;
private int scale;
public ExactLong(String dataStr) {
if (dataStr.indexOf(".") == -1) {// 如123456
data = Long.parseLong(dataStr);
scale = 0;
return;
}
if (dataStr.indexOf(".") != dataStr.lastIndexOf(".")) {// 如123.456.7
throw new IllegalArgumentException(dataStr + " can not cast to long!");
} else {// 如12345.678
scale = dataStr.length() - dataStr.indexOf(".") - 1;
long beforePoint = Long.parseLong(dataStr.substring(0, dataStr.indexOf(".")));// 小数点之前的数
long afterPoint = Long.parseLong(dataStr.substring(dataStr.indexOf(".") + 1, dataStr.length()));// 小数点之后的数
data = (long) (beforePoint * Math.pow(10, scale) + afterPoint);
}
}
public long getData() {
return data;
}
public void setData(long data) {
this.data = data;
}
public int getScale() {
return scale;
}
public void setScale(int scale) {
this.scale = scale;
}
}
Java中使用long类型实现精确的四则运算的更多相关文章
- Java中的Bigdecimal类型运算
Java中的Bigdecimal类型运算 双精度浮点型变量double可以处理16位有效数.在实际应用中,需要对更大或者更小的数进行运算和处理.Java在java.math包中提 供的API类BigD ...
- 理解Java中的字符串类型
1.Java内置对字符串的支持: 所谓的内置支持,即不用像C语言通过char指针实现字符串类型,并且Java的字符串编码是符合Unicode编码标准,这也意味着不用像C++那样通过使用string和w ...
- Java中的枚举类型详解
枚举类型介绍 枚举类型(Enumerated Type) 很早就出现在编程语言中,它被用来将一组类似的值包含到一种类型当中.而这种枚举类型的名称则会被定义成独一无二的类型描述符,在这一点上和常量的定义 ...
- 关于java中的事件类型
java中的Date是为了证明:天才的程序员也会犯错: java中的Calendar是为了证明:普通的程序员也会犯错. ———————————————————— stackoverflow上大部分都推 ...
- Java中的集合类型的继承关系图
Java中的集合类型的继承关系图
- java中,字符串类型的时间数据怎样转换成date类型。
将字符串类型的时间转换成date类型可以使用SimpleDateFormat来转换,具体方法如下:1.定义一个字符串类型的时间:2.创建一个SimpleDateFormat对象并设置格式:3.最后使用 ...
- 1 Java中的时间类型
总结:sql中的时间转 util的时间直接赋值即可:反过来,必须先吧util下的时间转换成毫秒,再通过sql的构造器生成sql的时间格式. 1 Java中的时间类型 java.sql包下给出三个与数据 ...
- 详解java中的byte类型
Java也提供了一个byte数据类型,并且是基本类型.java byte是做为最小的数字来处理的,因此它的值域被定义为-128~127,也就是signed byte.下面这篇文章主要给大家介绍了关于j ...
- MySql数据库类型bit等与JAVA中的对应类型【布尔类型怎么存】
用char(1):可以表示字符或者数字,但是不能直接计算同列的值.存储消耗1个字节 用tinyint:只能表示数字,可以直接计算,存储消耗2个字节 用bit: 只能表示0或1,不能计算,存储消耗小于等 ...
随机推荐
- wrapper x64 版本发布到centos
背景: 项目需要在spark任务提交服务器节点上自动提交任务到spark集群上.因此创建了一个固定时间监控任务项目,使用timer定时监控oracle数据库中是否有spark提交任务,如果有spark ...
- C# QQ & 163 邮件发送
这篇文章的目的并不是说明如果进行右键的发送,因为在.net 坝坝的怀抱下邮件发送的功能实现并不会很难,当然邮件发送的代码,还是会贴上的,昨天在写一个邮件发送的功能,我直接找到了原来的代码,想着直接就可 ...
- hue集成hbase出现TSocket read 0 bytes
解决办法:修改hbase的配置文件 添加以下配置 https://stackoverflow.com/questions/20415493/api-error-tsocket-read-0-bytes ...
- css3 box-shadow阴影(外阴影与外发光)
基础说明: 外阴影:box-shadow: X轴 Y轴 Rpx color; 属性说明(顺序依次对应): 阴影的X轴(可以使用负值) 阴影的Y轴(可以使用负值) 阴影 ...
- Gogs搭建教程-极易搭建的自助 Git 服务
前言 最近在搭建自己的持续集成,网上非常多的教程都是使用的gitlab作为代码管理工具,但是gitlab非常重,而且吃配置,而gogs非常轻便简介,成为不二之选. 操作系统:Centos 7.0 一. ...
- Linux使用踩坑记
Ubuntu安装坑: 1.对于新手第一次安装ubuntu,特殊情况会出现因为分辨率问题导致安装界面不全,无法进行下一步操作. 解决方案:使用alt+鼠标左键拖动屏幕Linux文件名乱码问题: 2.因为 ...
- [CEOI2008]order
Description 有N个工作,M种机器,每种机器你可以租或者买过来. 每个工作包括若干道工序,每道工序需要某种机器来完成,你可以通过购买或租用机器来完成. 现在给出这些参数,求最大利润 Solu ...
- bzoj 2560: 串珠子
Description 铭铭有n个十分漂亮的珠子和若干根颜色不同的绳子.现在铭铭想用绳子把所有的珠子连接成一个整体. 现在已知所有珠子互不相同,用整数1到n编号.对于第i个珠子和第j个珠子,可以选择不 ...
- ●BZOJ 1069 [SCOI2007]最大土地面积
题链: http://www.lydsy.com/JudgeOnline/problem.php?id=1069 题解: 计算几何,凸包,旋转卡壳 其实和这个题差不多,POJ 2079 Triangl ...
- 2014-10-30NOIP复习题1
Problem 1 Graph (graph.cpp/c/pas) [题目描述] 给出 N 个点,M 条边的有向图,对于每个点 v,求 A(v) 表示从点 v 出发,能到达的编号最大的点. [输入格式 ...