提问人:Deinumite 提问时间:11/27/2008 最后编辑:MakotoDeinumite 更新时间:9/6/2023 访问量:260705
在 Java 中使用 double 保持精度
Retain precision with double in Java
问:
public class doublePrecision {
public static void main(String[] args) {
double total = 0;
total += 5.6;
total += 5.8;
System.out.println(total);
}
}
上面的代码打印:
11.399999999999
我如何让它只打印(或能够将其用作)11.4?
答:
将所有内容乘以 100 并将其存储为美分。
评论
可以肯定的是,你可以把它变成一个三行的例子。:)
如果需要精确的精度,请使用 BigDecimal。否则,您可以使用整数乘以 10 ^ 来表示您想要的任何精度。
双精度是 Java 源代码中十进制数的近似值。你会看到双精度值(二进制编码值)和源值(十进制编码)之间不匹配的结果。
Java 正在生成最接近的二进制近似值。您可以使用 java.text.DecimalFormat 来显示更美观的十进制值。
使用 BigDecimal。它甚至允许您指定舍入规则(如ROUND_HALF_EVEN,如果两者的距离相同,则通过四舍五入到偶数邻居来最大程度地减少统计误差;即 1.5 和 2.5 四舍五入为 2)。
请注意,如果您使用有限精度的十进制算术,并且想要处理 1/3,您也会遇到同样的问题:0.3333333333 * 3 是 0.9999999999,而不是 1.000000000。
不幸的是,5.6、5.8 和 11.4 不是二进制中的整数,因为它们涉及五分之一。因此,它们的浮点表示并不精确,就像 0.3333 不完全是 1/3 一样。
如果您使用的所有数字都是非重复的小数,并且您想要精确的结果,请使用 BigDecimal。或者正如其他人所说,如果你的值就像货币一样,它们都是 0.01 或 0.001 的倍数,或者其他什么,那么将所有内容乘以 10 的固定幂并使用 int 或 long(加法和减法是微不足道的:注意乘法)。
但是,如果您对二进制计算感到满意,但您只想以稍微友好的格式打印出来,请尝试或 .在格式字符串中,指定小于双精度精度的精度。对于 10 个有效数字,例如,11.3999999999999 是 11.4,因此在二进制结果非常接近只需要几位小数位的值的情况下,结果几乎同样准确且更易于人类阅读。java.util.Formatter
String.format
指定的精度在一定程度上取决于你对数字做了多少数学运算——一般来说,你做的越多,累积的误差就越多,但有些算法的积累速度比其他算法快得多(它们被称为“不稳定”,而不是“稳定”关于四舍五入误差)。如果你所做的只是添加几个值,那么我猜只要把精度降低到小数点后一位,就能解决问题。实验。
评论
正如其他人所提到的,如果你想获得 11.4 的精确表示形式,你可能想要使用 BigDecimal
类。
现在,稍微解释一下为什么会发生这种情况:
Java 中的 和 原始类型是浮点数,其中数字存储为分数和指数的二进制表示。float
double
更具体地说,双精度浮点值(如类型)是 64 位值,其中:double
- 1 位表示符号(正或负)。
- 指数为 11 位。
- 有效数字为 52 位(作为二进制的小数部分)。
这些部分组合在一起以生成值的表示形式。double
(来源:维基百科:双精度)
有关如何在 Java 中处理浮点值的详细说明,请参见 Java 语言规范的第 4.2.3 节:浮点类型、格式和值。
、、 类型是定点数,是数的精确表示。与定点数不同,浮点数有时(可以安全地假设“大部分时间”)无法返回数字的精确表示。这就是为什么你最终得到的结果。byte
char
int
long
11.399999999999
5.6 + 5.8
当需要精确的值(如 1.5 或 150.1005)时,您需要使用一种定点类型,该类型将能够准确表示数字。
正如已经多次提到的,Java 有一个 BigDecimal
类,它将处理非常大的数字和非常小的数字。
从该类的 Java API 参考:BigDecimal
变 任意精度有符号十进制 数字。BigDecimal 由 任意精度整数未缩放 value 和 32 位整数刻度。如果 零或正,刻度为 右侧的位数 小数点。如果为负数,则 未缩放的数字值为 乘以 10 的幂 否定规模。的值 数字表示 因此,BigDecimal 是 (unscaledValue × 10^ 刻度)。
关于 Stack Overflow 上有许多关于浮点数及其精度的问题。以下是您可能感兴趣的相关问题列表:
- 为什么我看到一个双精度变量初始化为21.399999618530273的某个值,如21.4?
- 如何在 C++ 中打印非常大的数字
- 浮点是如何存储的?什么时候重要?
- 使用浮点数或小数作为会计应用程序美元金额?
如果你真的想深入了解浮点数的细节,请看一下每个计算机科学家都应该知道的关于浮点运算的知识。
评论
BigDecimal
double
正如其他人所指出的,并非所有十进制值都可以表示为二进制,因为十进制基于 10 的幂,而二进制基于 2 的幂。
如果精度很重要,请使用 BigDecimal,但如果您只想要友好的输出:
System.out.printf("%.2f\n", total);
会给你:
11.40
看看BigDecimal,它处理了像这样的浮点运算问题。
新调用如下所示:
term[number].coefficient.add(co);
使用 setScale() 设置要使用的小数位数精度。
使用 java.math.BigDecimal
双精度在内部是二进制分数,因此它们有时无法将十进制分数表示为精确的小数点。
评论
你不能,因为 7.3 在二进制中没有有限的表示形式。你能得到的最接近的是 2054767329987789/2**48 = 7.3+1/1407374883553280。
请查看 http://docs.python.org/tutorial/floatingpoint.html 以获得进一步的解释。(它在Python网站上,但Java和C++有相同的“问题”。
解决方案取决于您的问题到底是什么:
- 如果您只是不喜欢看到所有这些噪声数字,请修复您的字符串格式。不要显示超过 15 个有效数字(或浮点数为 7 个)。
- 如果你的数字不准确,破坏了“if”语句之类的东西,那么你应该写 if (abs(x - 7.3) < TOLERANCE) 而不是 if (x == 7.3)。
- 如果你在和钱打交道,那么你可能真正想要的是小数点定点。存储整数美分或货币的最小单位。
- (不太可能)如果需要超过 53 个有效位(15-16 个有效数字)的精度,请使用高精度浮点类型,如 BigDecimal。
评论
您遇到了 double 类型的精度限制。
Java.Math 有一些任意精度的算术工具。
评论
计算机以二进制形式存储数字,实际上不能准确表示 33.3333333333 或 100.0 等数字。这是使用双打的棘手问题之一。在向用户显示答案之前,您必须将答案四舍五入。幸运的是,在大多数应用程序中,您无论如何都不需要那么多小数位。
评论
浮点数与实数的不同之处在于,对于任何给定的浮点数,都有一个更高的浮点数。与整数相同。1 和 2 之间没有整数。
无法将 1/3 表示为浮点数。它下面有一个浮子,上面有一个浮子,它们之间有一定的距离。1/3 在那个空间里。
Apfloat for Java 声称可以使用任意精度的浮点数,但我从未使用过它。可能值得一看。http://www.apfloat.org/apfloat_java/
在 Java 浮点高精度库之前,这里也问过类似的问题
例如,当您输入双精度数时,您得到的值实际上是最接近的可表示双精度值,确切地说是:33.33333333333333
33.3333333333333285963817615993320941925048828125
将其除以 100 得到:
0.333333333333333285963817615993320941925048828125
这也不能表示为双精度数字,因此它再次四舍五入到最接近的可表示值,即:
0.3333333333333332593184650249895639717578887939453125
当您打印出此值时,它会再次四舍五入到 17 位十进制数字,从而给出:
0.33333333333333326
评论
如果你真的需要精确数学,你可能想考虑使用 java 的 java.math.BigDecimal 类。这是一篇来自 Oracle/Sun 的关于 BigDecimal 案例的好文章。虽然你永远不能像有人提到的那样表示 1/3,但你可以决定你希望结果的精确程度。setScale() 是你的朋友。:)
好的,因为我现在手上有太多时间,这里有一个与您的问题相关的代码示例:
import java.math.BigDecimal;
/**
* Created by a wonderful programmer known as:
* Vincent Stoessel
* [email protected]
* on Mar 17, 2010 at 11:05:16 PM
*/
public class BigUp {
public static void main(String[] args) {
BigDecimal first, second, result ;
first = new BigDecimal("33.33333333333333") ;
second = new BigDecimal("100") ;
result = first.divide(second);
System.out.println("result is " + result);
//will print : result is 0.3333333333333333
}
}
为了插入我最喜欢的新语言 Groovy,这里有一个更简洁的例子:
import java.math.BigDecimal
def first = new BigDecimal("33.33333333333333")
def second = new BigDecimal("100")
println "result is " + first/second // will print: result is 0.33333333333333
如果只想将值作为分数进行处理,则可以创建一个包含分子和分母字段的 Fraction 类。
编写加法、减法、乘法和除法的方法以及 toDouble 方法。这样,您可以在计算过程中避免浮点数。
编辑:快速实施,
public class Fraction {
private int numerator;
private int denominator;
public Fraction(int n, int d){
numerator = n;
denominator = d;
}
public double toDouble(){
return ((double)numerator)/((double)denominator);
}
public static Fraction add(Fraction a, Fraction b){
if(a.denominator != b.denominator){
double aTop = b.denominator * a.numerator;
double bTop = a.denominator * b.numerator;
return new Fraction(aTop + bTop, a.denominator * b.denominator);
}
else{
return new Fraction(a.numerator + b.numerator, a.denominator);
}
}
public static Fraction divide(Fraction a, Fraction b){
return new Fraction(a.numerator * b.denominator, a.denominator * b.numerator);
}
public static Fraction multiply(Fraction a, Fraction b){
return new Fraction(a.numerator * b.numerator, a.denominator * b.denominator);
}
public static Fraction subtract(Fraction a, Fraction b){
if(a.denominator != b.denominator){
double aTop = b.denominator * a.numerator;
double bTop = a.denominator * b.numerator;
return new Fraction(aTop-bTop, a.denominator*b.denominator);
}
else{
return new Fraction(a.numerator - b.numerator, a.denominator);
}
}
}
评论
numerator
denominator
int
不要使用 BigDecimal 浪费你的 efford。在 99.99999% 的情况下,您不需要它。Java double 类型是近似的,但在几乎所有情况下,它都足够精确。请注意,您在第 14 位有效数字处有错误。这真的可以忽略不计!
要获得良好的输出,请使用:
System.out.printf("%.2f\n", total);
评论
private void getRound() {
// this is very simple and interesting
double a = 5, b = 3, c;
c = a / b;
System.out.println(" round val is " + c);
// round val is : 1.6666666666666667
// if you want to only two precision point with double we
// can use formate option in String
// which takes 2 parameters one is formte specifier which
// shows dicimal places another double value
String s = String.format("%.2f", c);
double val = Double.parseDouble(s);
System.out.println(" val is :" + val);
// now out put will be : val is :1.67
}
为什么不使用 Math 类中的 round() 方法?
// The number of 0s determines how many digits you want after the floating point
// (here one digit)
total = (double)Math.round(total * 10) / 10;
System.out.println(total); // prints 11.4
如果除了使用双精度值之外别无选择,可以使用以下代码。
public static double sumDouble(double value1, double value2) {
double sum = 0.0;
String value1Str = Double.toString(value1);
int decimalIndex = value1Str.indexOf(".");
int value1Precision = 0;
if (decimalIndex != -1) {
value1Precision = (value1Str.length() - 1) - decimalIndex;
}
String value2Str = Double.toString(value2);
decimalIndex = value2Str.indexOf(".");
int value2Precision = 0;
if (decimalIndex != -1) {
value2Precision = (value2Str.length() - 1) - decimalIndex;
}
int maxPrecision = value1Precision > value2Precision ? value1Precision : value2Precision;
sum = value1 + value2;
String s = String.format("%." + maxPrecision + "f", sum);
sum = Double.parseDouble(s);
return sum;
}
简短的回答:始终使用 BigDecimal 并确保使用带有 String 参数的构造函数,而不是双精度参数。
回到您的示例,以下代码将根据需要打印 11.4。
public class doublePrecision {
public static void main(String[] args) {
BigDecimal total = new BigDecimal("0");
total = total.add(new BigDecimal("5.6"));
total = total.add(new BigDecimal("5.8"));
System.out.println(total);
}
}
/*
0.8 1.2
0.7 1.3
0.7000000000000002 2.3
0.7999999999999998 4.2
*/
double adjust = fToInt + 1.0 - orgV;
// The following two lines works for me.
String s = String.format("%.2f", adjust);
double val = Double.parseDouble(s);
System.out.println(val); // output: 0.8, 0.7, 0.7, 0.8
您可以执行以下操作!
System.out.println(String.format("%.12f", total));
如果在此处更改十进制值 %.12f
到目前为止,我将其理解为主要目标,以纠正错误。double
double
寻找我的解决方案如何从“近似”错误值中获得正确的值 - 如果它是真正的浮点数,它会舍入最后一位数字 - 从所有数字开始计数 - 在点之前计数并尝试在点之后保持最大可能的数字 - 希望它在大多数情况下足够精确:
public static double roundError(double value) {
BigDecimal valueBigDecimal = new BigDecimal(Double.toString(value));
String valueString = valueBigDecimal.toPlainString();
if (!valueString.contains(".")) return value;
String[] valueArray = valueString.split("[.]");
int places = 16;
places -= valueArray[0].length();
if ("56789".contains("" + valueArray[0].charAt(valueArray[0].length() - 1))) places--;
//System.out.println("Rounding " + value + "(" + valueString + ") to " + places + " places");
return valueBigDecimal.setScale(places, RoundingMode.HALF_UP).doubleValue();
}
我知道这是很长的代码,肯定不是最好的,也许有人可以修复它以使其更优雅。无论如何,它正在工作,请参阅示例:
roundError(5.6+5.8) = 11.399999999999999 = 11.4
roundError(0.4-0.3) = 0.10000000000000003 = 0.1
roundError(37235.137567000005) = 37235.137567
roundError(1/3) 0.3333333333333333 = 0.333333333333333
roundError(3723513756.7000005) = 3.7235137567E9 (3723513756.7)
roundError(3723513756123.7000005) = 3.7235137561237E12 (3723513756123.7)
roundError(372351375612.7000005) = 3.723513756127E11 (372351375612.7)
roundError(1.7976931348623157) = 1.797693134862316
上一个:Java 浮点计算行为差异
评论