可以准确转换为美分多头的最大美元金额双倍

如何解决可以准确转换为美分多头的最大美元金额双倍

我正在编写一个带有变量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 maxValuedouble minValue以始终获得准确的返回值吗?

解决方法

您可以将BigDecimal用作临时持有人

如果您的双精度数很大(介于Double.MAX_VALUE / 100.0 + 1Double.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:所有典型的数据类型(包括421/3)都无法准确表示double。他们只能做(或多或少接近)近似。结果是与3的乘法运算未完全给出整数BigDecimal。很少有语言提供“比率”类型,能够用分子和分母表示数字,从而给出准确的结果。
  • 1/3:由于具有2的幂,所以11/1024可以轻松地进行精确表示。 float也可以,但需要10个小数位数。
  • double:由于十进制小数(可以重写为BigDecimal),14.99可以很容易地做到这一点(正是这样做的目的),1499/100和{{ 1}}只能给出一个近似值。
  • BigDecimal:我不知道支持非理性数字的任何语言-我什至不知道这怎么可能(除了象征性地对待PI和E等流行的非理性人物)。
  • floatdoublePI可以做到,double可以近似(最后13位是垃圾),123456789123456789123456789和{{ 1}}完全失败。

面对现实:每种数据类型都有一类可以精确表示的数字,其中计算可以提供精确的结果,而其他类最多可以提供近似值。

所以问题应该是:

  • 这里要显示的数字类型和范围是什么?
  • 可以近似吗?如果可以,应该接近多远?
  • 符合我要求的数据类型是什么?
,

您查看的是可能的最大长整数,而最大的可能是两倍较小。计算(amountUsd * 100.0)会导致翻倍(然后被强制转换成long)。

您应确保(amountUsd * 100.0)不能大于最大的两倍数9007199254740992

,

在Java中,使用最大的double是:70368744177663.99。

您在double中拥有的是64位(8字节)来表示:

  1. 小数和整数
  2. +/-

问题是使它不接近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 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-