如何解决可以准确转换为美分多头的最大美元金额双倍
我正在编写一个带有变量long balance
的银行程序,以将美分存储在帐户中。当用户输入金额时,我有一种方法可以将美元转换为美分:
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
的小数位数是否超过2个,以避免无法准确转换的输入(例如20.001美元并不完全是2000美分)。对于此示例代码,假定所有输入都有0、1或2个小数。
起初,我查看了Long.MAX_VALUE
(9223372036854775807美分),并假设double maxValue = 92233720368547758.07
是正确的,但是它给了我很大的舍入误差:
convertFromUsd(92233720368547758.07)
给出输出9223372036854775807
convertFromUsd(92233720368547758.00)
给出相同的输出922337203685477575807
我应该设置double maxValue
和double minValue
以始终获得准确的返回值吗?
解决方法
您可以将BigDecimal
用作临时持有人
如果您的双精度数很大(介于Double.MAX_VALUE / 100.0 + 1
和Double.MAX_VALUE
之间),则usd * 100.0
的计算将导致双精度数溢出。
但是,由于您知道<any double> * 100
的所有可能结果都将适合很长的时间,因此您可以使用BigDecimal
作为计算的临时持有人。
此外,BigDecimal
类定义了两个很方便的方法:
通过使用BigDecimal
,您完全不必指定最大值->可以将表示美元的任何给定双精度值转换为代表美分的长值(假设您不必处理分数)。
double usd = 123.45;
long cents = BigDecimal.valueOf(usd).movePointRight(2).setScale(0).longValueExact();
注意:请记住,双起首不能存储确切的USD信息。通过将double转换为BigDecimal
,无法恢复丢失的信息。
临时BigDecimal
给您带来的唯一好处是usd * 100
的计算不会溢出。
首先,将double
用作货币金额是有风险的。
TL; DR
我建议保持在$17,592,186,044,416
以下。
数字(double
类型)的浮点表示形式不使用小数部分(1 / 10、1 / 100、1 / 1000,...),而是二进制数(例如1/128) ,1/256)。因此,double
号将永远不会准确地打到$1.99
之类的东西。大多数情况下它将关闭。
希望从十进制数字输入(“ 1.99”)到double
数字的转换将以最接近的二进制近似值结束,即比精确的十进制值高或低的一小部分。
要能够正确表示从$xxx.00
到$xxx.99
的100个不同的美分值,您需要一个二进制分辨率,其中至少可以表示小数部分的128个不同值,这意味着有效位对应于1/128(或更好),这意味着至少有7个尾随位必须专用于小数美元。
double
格式的尾数有效为53位。如果您需要7位小数,则最多可以将46位用于整数部分,这意味着您必须将绝对限制保持在2 ^ 46美元($70,368,744,177,664.00
,70万亿美元)以下。
作为预防措施,我不相信过多地从十进制数字转换为double
的最佳舍入属性,因此我将再花两位用于小数部分,结果限制为2 ^ 44美元,$17,416
。
代码警告
您的代码中有一个缺陷:
return (long) (amountUsd * 100.0);
如果double
的值介于两个精确的美分之间,则它将截断为下一个较低的美分,例如“ 123456789.23”可能变成123456789.229...
的{{1}},而被截断成double
的{{1}}分。
您最好使用
12345678922
这将以最接近的分数值结束,很可能是“正确的”分数值。
编辑:
“精度”备注
您通常会读到浮点数不精确的陈述,然后在下一句话中,作者主张long
或类似表示形式是精确的。
这样的语句的有效性取决于您要表示的数字的类型。
当今计算中使用的所有数字表示系统对于某些类型的数字都是精确的,而对于其他类型的数字则是不精确的。让我们以数学中的一些示例数字为例,看看它们如何适合某些典型的数据类型:
-
return Math.round(amountUsd * 100.0);
:几乎所有类型都可以精确表示一个小整数。 -
BigDecimal
:所有典型的数据类型(包括42
和1/3
)都无法准确表示double
。他们只能做(或多或少接近)近似。结果是与3的乘法运算未完全给出整数BigDecimal
。很少有语言提供“比率”类型,能够用分子和分母表示数字,从而给出准确的结果。 -
1/3
:由于具有2的幂,所以1
和1/1024
可以轻松地进行精确表示。float
也可以,但需要10个小数位数。 -
double
:由于十进制小数(可以重写为BigDecimal
),14.99
可以很容易地做到这一点(正是这样做的目的),1499/100
和{{ 1}}只能给出一个近似值。 -
BigDecimal
:我不知道支持非理性数字的任何语言-我什至不知道这怎么可能(除了象征性地对待PI和E等流行的非理性人物)。 -
float
:double
和PI
可以做到,double可以近似(最后13位是垃圾),123456789123456789123456789
和{{ 1}}完全失败。
面对现实:每种数据类型都有一类可以精确表示的数字,其中计算可以提供精确的结果,而其他类最多可以提供近似值。
所以问题应该是:
- 这里要显示的数字类型和范围是什么?
- 可以近似吗?如果可以,应该接近多远?
- 符合我要求的数据类型是什么?
您查看的是可能的最大长整数,而最大的可能是两倍较小。计算(amountUsd * 100.0)
会导致翻倍(然后被强制转换成long)。
您应确保(amountUsd * 100.0)
不能大于最大的两倍数9007199254740992
。
在Java中,使用最大的double是:70368744177663.99。
您在double中拥有的是64位(8字节)来表示:
- 小数和整数
- +/-
问题是使它不接近0.99,因此整数部分为46位,其余部分用于小数点。
您可以使用以下代码进行测试:
double biggestPossitiveNumberInDouble = 70368744177663.99;
for(int i=0;i<130;i++){
System.out.printf("%.2f\n",biggestPossitiveNumberInDouble);
biggestPossitiveNumberInDouble=biggestPossitiveNumberInDouble-0.01;
}
如果将1添加到maximumPossitiveNumberInDouble中,您将看到它开始四舍五入并失去精度。 还要注意减去0.01时的舍入误差。
第一次迭代
70368744177663.99
70368744177663.98
70368744177663.98
70368744177663.97
70368744177663.96
...
在这种情况下,最好的方法是不将其解析为两倍:
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);
这样,您就不会遇到double的限制,而只是long的限制。
但最终还是要使用为货币创建的数据类型。
,浮点值(float,double)与整数值(int,long)的存储方式不同,虽然double可以存储非常大的值,但对于存储金额却不利,因为随着小数位数的增加或增加,它们的精度会降低。数字有。
查看How many significant digits do floats and doubles have in java?,以获取有关浮点有效数字的更多信息
双精度数是15个有效数字,有效数字计数是从第一个非零数字开始的数字总数。 (有关更好的解释,请参见https://en.wikipedia.org/wiki/Significant_figures 重要数字规则解释)
因此,在您的方程式中应包含美分,并确保您准确无误,您希望最大值不超过13个整数位和2个小数位。
在处理货币时,最好不要使用浮点值。请查看有关使用BigDecimal存储货币的本文:https://medium.com/@cancerian0684/which-data-type-would-you-choose-for-storing-currency-values-like-trading-price-dd7489e7a439
如前所述,用户正在输入金额,您可以将其读为String而不是浮点值,并将其传递到BigDecimal。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。