Java 可以准确转换为美分(长)的最大美元金额(双倍)
我正在编写一个银行程序,其中包含一个变量Java 可以准确转换为美分(长)的最大美元金额(双倍),java,types,currency,Java,Types,Currency,我正在编写一个银行程序,其中包含一个变量长余额,用于在帐户中存储美分。当用户输入金额时,我有一种从美元到美分的转换方法: public static long convertFromUsd (double amountUsd) { if(amountUsd <= maxValue || amountUsd >= minValue) { return (long) (amountUsd * 100.0) } else { //no conversio
长余额
,用于在帐户中存储美分。当用户输入金额时,我有一种从美元到美分的转换方法:
public static long convertFromUsd (double amountUsd) {
if(amountUsd <= maxValue || amountUsd >= minValue) {
return (long) (amountUsd * 100.0)
} else {
//no conversion (throws an exception, but I'm not including that part of the code)
}
}
公共静态长期转换美元(双倍金额美元){
如果(amountUsd=最小值){
回报(长期)(金额USD*100.0)
}否则{
//无转换(抛出异常,但我不包括代码的这一部分)
}
}
在我的实际代码中,我还检查了amountUsd
的小数不超过2,以避免无法准确转换的输入(例如20.001美元不完全是2000美分)。对于这个示例代码,假设所有输入都有0、1或2个小数
起初,我查看了Long.MAX_VALUE
(9223372036854775807美分),并假设double maxValue=92233720368547758.07
是正确的,但它给了我大量的舍入误差:
convertFromUsd(92233720368547758.07)
输出9223372036854775807
convertFromUsd(92233720368547758.00)
给出相同的输出9223372036854775807
我应该如何设置
double maxValue
和double minValue
以始终获得准确的返回值?您查看了可能的最大长数值,而可能的最大双精度则较小。计算(amountUsd*100.0)
会产生一个double(之后会被转换为long)
您应该确保
(amountUsd*100.0)
不能大于最大的double,也就是说。使用double,在Java中最大的是:70368744177663.99
double中的值是64位(8字节),表示:
double biggestPossitiveNumberInDouble = 70368744177663.99;
for(int i=0;i<130;i++){
System.out.printf("%.2f\n", biggestPossitiveNumberInDouble);
biggestPossitiveNumberInDouble=biggestPossitiveNumberInDouble-0.01;
}
在这种情况下,最好的方法是不要解析为double:
System.out.println("Enter amount:");
String input = new Scanner(System.in).nextLine();
int indexOfDot = input.indexOf('.');
if (indexOfDot == -1) indexOfDot = input.length();
int validInputLength = indexOfDot + 3;
if (validInputLength > input.length()) validInputLength = input.length();
String validInput = input.substring(0,validInputLength);
long amout = Integer.parseInt(validInput.replace(".", ""));
System.out.println("Converted: " + amout);
这样一来,你就不会遇到双重限制,而只会遇到长期限制
但最终将使用一个为货币制作的数据类型。您可以使用BigDecimal
作为临时持有者
如果您有一个非常大的双精度(介于double.MAX_VALUE/100.0+1
和double.MAX_VALUE
之间)计算usd*100.0
将导致双精度溢出
但是既然你知道,
通过使用BigDecimal
您根本不必费心指定一个最大值->任何表示美元的给定双精度可以转换为表示美分的长数值(假设您不必处理美分分数)
双美元=123.45;
long cents=BigDecimal.valueOf(usd).movePointRight(2).setScale(0).longValueExact();
注意:请记住,double首先无法存储准确的美元信息。通过将双精度转换为BigDecimal
,无法恢复丢失的信息。
临时BigDecimal
给您的唯一好处是usd*100
的计算不会溢出。首先,对货币金额使用double
是有风险的
TL;博士
我建议保持在17592186044416美元以下
数字的浮点表示法(double
type)不使用小数(1/10、1/100、1/1000等),而是使用二进制(例如1/128、1/256)。因此,double
数字永远不会精确地命中像$1.99
这样的值。在大多数情况下,它将被关闭一小部分
希望从十进制数字输入(“1.99”)到双精度
数字的转换将以最接近的二进制近似值结束,即比精确的十进制值高或低一小部分
为了能够正确表示从$xxx.00
到$xxx.99
的100个不同的分数值,您需要一个二进制分辨率,其中至少可以表示小数部分的128个不同值,这意味着最低有效位对应于1/128(或更好),这意味着至少7个尾随位必须专用于小数美元
double
格式实际上有53位作为尾数。如果分数需要7位,则最多可以将46位用于整数部分,这意味着您必须保持低于2^46美元($70368744177664.00
,70万亿)的绝对限制
作为预防措施,我不太相信将十进制数字转换为双精度的最佳舍入特性,因此我会为小数部分多花两位,结果限制为2^44美元,17592186044416美元
代码警告
您的代码中有一个缺陷:
return (long) (amountUsd * 100.0);
如果double
值介于两个精确的美分之间,这将向下截断到下一个较低的美分,这意味着例如“123456789.23”可能会变成123456789.229…
作为double
而被截断为12345678922
作为long
的美分
你最好使用
return Math.round(amountUsd * 100.0);
这将以最接近的美分值结束,很可能是“正确”的值
编辑:
关于“精确性”的评论
你经常读到浮点数不精确的陈述,然后在下一句中,作者主张BigDecimal
或类似的表示法是精确的
此类语句的有效性取决于要表示的数字类型
当今计算机中使用的所有数字表示系统对于某些类型的数字都是精确的,而对于其他类型的数字则是不精确的。让我们从数学中提取几个例子,看看它们适合于一些典型的数据类型:
<
return Math.round(amountUsd * 100.0);