BigDecimal 对大于 7 或 0 小数的数字给出意想不到的结果
BigDecimal gives unexpected results for numbers larger than 7 or 0 decimal numbers
在尝试计算 2 个物体的体积比时,我注意到计算中有些奇怪,这里有一个示例,您可以自己 运行:
public class TestApplication {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.0);
BigDecimal second = BigDecimal.valueOf(13196000.0);
System.out.println("First: " + first);
System.out.println("Second: " + second);
System.out.println("Division: " + first.divide(second, RoundingMode.HALF_UP).doubleValue());
}
}
结果是:
First: 2.1099E+7
Second: 1.3196E+7
Division: 0.0
我有 3 种方法可以让它给我预期的结果
1.如果我将小数部分从 0 更改为 1(或任何非 0 数字):
First: 21099000.1
Second: 13196000.1
Division: 1.6
2。如果我预先划分数字(将它们设为 7 位数字而不是 8 位数字):
First: 2109900.0
Second: 1319600.0
Division: 1.6
3。如果我指定一个比例进行除法 (first.divide(second, 0, RoundingMode.HALF_UP
):
First: 2.1099E+7
Second: 1.3196E+7
Division: 2.0
我认为 BigDecimal 是由一个整数支持的,而我使用的数字远低于 20 亿。谁能解释一下是什么让这 3 个案例与原始结果不同?
A BigDecimal
consists of an arbitrary precision integer unscaled value and a 32-bit integer scale.
The value of the number represented by the BigDecimal is therefore (unscaledValue × 10-scale).
此外,关于 divide method:
Returns a BigDecimal whose value is (this / divisor)
, and whose scale is this.scale()
您可以使用 unscaledValue
和 scale
方法验证数字的非标度值和标度:
var first = BigDecimal.valueOf(21099000.0);
// ==> 2.1099E+7
first.unscaledValue()
// ==> 21099
first.scale()
// ==> -3
事实证明,unscaledValue 为 21099,scale -3。如您所料,这个数字在数学上等于 21099*10^3 = 21099000,但是因为它的 scale
为 -3,这意味着 first.divide(second, RoundingMode)
的比例也将为 -3。
换句话说,divide()
的结果必须四舍五入到1000的倍数。
除法的真实值约为 1.599。根据舍入方式RoundingMode.HALF_UP 必须向下舍入,为0.
要获得不同的行为,您必须将自定义 scale
值传递给 divide
,或者更改 first
的比例。例如,您可以更改比例:
first = first.setScale(2)
或者您可以以保证设定比例的方式创建数字:
first = new BigDecimal("21099000"); // sets scale to 0
or
first = new BigDecimal(21099000); // sets scale to 0
根据documentation,divide(BigDecimal divisor, RoundingMode roundingMode)
returns一个BigDecimal
,其值为(this / divisor),其标度为this.scale()
。
为什么得到 21099000.1 / 13196000.1 的预期结果?
检查以下代码的结果:
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.1);
BigDecimal second = BigDecimal.valueOf(13196000.1);
System.out.println("First: " + first + ", Scale: " + first.scale());
System.out.println("Second: " + second + ", Scale: " + second.scale());
// 21099000.0 / 13196000.0 = 1.5988936041
System.out.println(BigDecimal.valueOf(1.5988936041).setScale(first.scale(), RoundingMode.HALF_UP));
}
}
输出:
First: 21099000.1, Scale: 1
Second: 13196000.1, Scale: 1
1.6
如您所见,JVM 已将 first
的比例选择为 1
,因此 divide
(即 1.5988936041
)的结果也设置为1
等于 1.6
和 RoundingMode.HALF_UP
.
为什么没有得到 21099000.0 / 13196000.0 的预期结果?
检查以下代码的结果:
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.0);
BigDecimal second = BigDecimal.valueOf(13196000.0);
System.out.println("First: " + first + ", Scale: " + first.scale());
System.out.println("Second: " + second + ", Scale: " + second.scale());
// 21099000.0 / 13196000.0 = 1.5988936041
System.out.println(BigDecimal.valueOf(1.5988936041).setScale(first.scale(), RoundingMode.HALF_UP));
}
}
输出:
First: 2.1099E+7, Scale: -3
Second: 1.3196E+7, Scale: -3
0E+3
如您所见,JVM 已将 first
的比例选择为 -3
,因此 divide
(即 1.5988936041
)的结果也设置为-3
等于 0
(或 0E+3
)和 RoundingMode.HALF_UP
.
如何改变这种行为?
如文档中所述,划分的比例设置为this.scale()
,这意味着如果将first
的比例设置为1
,则可以得到预期的结果。
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.0).setScale(1);
BigDecimal second = BigDecimal.valueOf(13196000.0);
System.out.println("First: " + first + ", Scale: " + first.scale());
System.out.println("Second: " + second + ", Scale: " + second.scale());
System.out.println("Division: " + first.divide(second, RoundingMode.HALF_UP).doubleValue());
}
}
输出:
First: 21099000.0, Scale: 1
Second: 1.3196E+7, Scale: -3
Division: 1.6
最常见的方式是什么?
最后一个例子效果很好,使用没有问题。但是,最常见的方法是使用 divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
.
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.0);
BigDecimal second = BigDecimal.valueOf(13196000.0);
System.out.println("First: " + first + ", Scale: " + first.scale());
System.out.println("Second: " + second + ", Scale: " + second.scale());
System.out.println("Division: " + first.divide(second, 1, RoundingMode.HALF_UP).doubleValue());
}
}
输出:
First: 2.1099E+7, Scale: -3
Second: 1.3196E+7, Scale: -3
Division: 1.6
在尝试计算 2 个物体的体积比时,我注意到计算中有些奇怪,这里有一个示例,您可以自己 运行:
public class TestApplication {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.0);
BigDecimal second = BigDecimal.valueOf(13196000.0);
System.out.println("First: " + first);
System.out.println("Second: " + second);
System.out.println("Division: " + first.divide(second, RoundingMode.HALF_UP).doubleValue());
}
}
结果是:
First: 2.1099E+7
Second: 1.3196E+7
Division: 0.0
我有 3 种方法可以让它给我预期的结果
1.如果我将小数部分从 0 更改为 1(或任何非 0 数字):
First: 21099000.1
Second: 13196000.1
Division: 1.6
2。如果我预先划分数字(将它们设为 7 位数字而不是 8 位数字):
First: 2109900.0
Second: 1319600.0
Division: 1.6
3。如果我指定一个比例进行除法 (first.divide(second, 0, RoundingMode.HALF_UP
):
First: 2.1099E+7
Second: 1.3196E+7
Division: 2.0
我认为 BigDecimal 是由一个整数支持的,而我使用的数字远低于 20 亿。谁能解释一下是什么让这 3 个案例与原始结果不同?
A
BigDecimal
consists of an arbitrary precision integer unscaled value and a 32-bit integer scale.The value of the number represented by the BigDecimal is therefore (unscaledValue × 10-scale).
此外,关于 divide method:
Returns a BigDecimal whose value is
(this / divisor)
, and whose scale isthis.scale()
您可以使用 unscaledValue
和 scale
方法验证数字的非标度值和标度:
var first = BigDecimal.valueOf(21099000.0);
// ==> 2.1099E+7
first.unscaledValue()
// ==> 21099
first.scale()
// ==> -3
事实证明,unscaledValue 为 21099,scale -3。如您所料,这个数字在数学上等于 21099*10^3 = 21099000,但是因为它的 scale
为 -3,这意味着 first.divide(second, RoundingMode)
的比例也将为 -3。
换句话说,divide()
的结果必须四舍五入到1000的倍数。
除法的真实值约为 1.599。根据舍入方式RoundingMode.HALF_UP 必须向下舍入,为0.
要获得不同的行为,您必须将自定义 scale
值传递给 divide
,或者更改 first
的比例。例如,您可以更改比例:
first = first.setScale(2)
或者您可以以保证设定比例的方式创建数字:
first = new BigDecimal("21099000"); // sets scale to 0
or
first = new BigDecimal(21099000); // sets scale to 0
根据documentation,divide(BigDecimal divisor, RoundingMode roundingMode)
returns一个BigDecimal
,其值为(this / divisor),其标度为this.scale()
。
为什么得到 21099000.1 / 13196000.1 的预期结果?
检查以下代码的结果:
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.1);
BigDecimal second = BigDecimal.valueOf(13196000.1);
System.out.println("First: " + first + ", Scale: " + first.scale());
System.out.println("Second: " + second + ", Scale: " + second.scale());
// 21099000.0 / 13196000.0 = 1.5988936041
System.out.println(BigDecimal.valueOf(1.5988936041).setScale(first.scale(), RoundingMode.HALF_UP));
}
}
输出:
First: 21099000.1, Scale: 1
Second: 13196000.1, Scale: 1
1.6
如您所见,JVM 已将 first
的比例选择为 1
,因此 divide
(即 1.5988936041
)的结果也设置为1
等于 1.6
和 RoundingMode.HALF_UP
.
为什么没有得到 21099000.0 / 13196000.0 的预期结果?
检查以下代码的结果:
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.0);
BigDecimal second = BigDecimal.valueOf(13196000.0);
System.out.println("First: " + first + ", Scale: " + first.scale());
System.out.println("Second: " + second + ", Scale: " + second.scale());
// 21099000.0 / 13196000.0 = 1.5988936041
System.out.println(BigDecimal.valueOf(1.5988936041).setScale(first.scale(), RoundingMode.HALF_UP));
}
}
输出:
First: 2.1099E+7, Scale: -3
Second: 1.3196E+7, Scale: -3
0E+3
如您所见,JVM 已将 first
的比例选择为 -3
,因此 divide
(即 1.5988936041
)的结果也设置为-3
等于 0
(或 0E+3
)和 RoundingMode.HALF_UP
.
如何改变这种行为?
如文档中所述,划分的比例设置为this.scale()
,这意味着如果将first
的比例设置为1
,则可以得到预期的结果。
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.0).setScale(1);
BigDecimal second = BigDecimal.valueOf(13196000.0);
System.out.println("First: " + first + ", Scale: " + first.scale());
System.out.println("Second: " + second + ", Scale: " + second.scale());
System.out.println("Division: " + first.divide(second, RoundingMode.HALF_UP).doubleValue());
}
}
输出:
First: 21099000.0, Scale: 1
Second: 1.3196E+7, Scale: -3
Division: 1.6
最常见的方式是什么?
最后一个例子效果很好,使用没有问题。但是,最常见的方法是使用 divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
.
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.0);
BigDecimal second = BigDecimal.valueOf(13196000.0);
System.out.println("First: " + first + ", Scale: " + first.scale());
System.out.println("Second: " + second + ", Scale: " + second.scale());
System.out.println("Division: " + first.divide(second, 1, RoundingMode.HALF_UP).doubleValue());
}
}
输出:
First: 2.1099E+7, Scale: -3
Second: 1.3196E+7, Scale: -3
Division: 1.6