vbe*_*nar 0 java data-conversion
我想将 float 值转换为 int 值,或者如果此转换不准确,则抛出异常。
我发现了以下建议:使用Math.round来转换,然后使用==来检查这些值是否相等。如果它们相等,则转换是精确的,否则不是。
但我发现了一个不起作用的例子。下面是演示此示例的代码:
String s = "2147483648";
float f = Float.parseFloat(s);
System.out.printf("f=%f\n", f);
int i = Math.round(f);
System.out.printf("i=%d\n", i);
System.out.printf("(f == i)=%s\n", (f == i));
Run Code Online (Sandbox Code Playgroud)
它输出:
f=2147483648.000000
i=2147483647
(f == i)=true
Run Code Online (Sandbox Code Playgroud)
我知道 2147483648 不适合整数范围,但我很惊讶==这些值返回 true。有更好的方法来比较 float 和 int 吗?我想可以将这两个值都转换为字符串,但对于这样一个原始函数来说,这会非常慢。
浮动是相当不精确的概念。它们也大多毫无意义,除非您此时运行在相当旧的硬件上,或者专门与在浮点中工作的系统和/或协议进行交互,或者在其规范中硬编码了“使用浮点”。这可能是正确的,但如果不是,请停止使用浮点数并开始使用- 除非您有double相当大的内存和性能差异为零,否则浮点数不太准确。float[]
使用intvs时,您的算法不会失败double-所有整数都可以完美地表示为double。
这里的根本错误是“静默转换”的概念以及java如何在那里采取一些故意的自由。
一般来说,在计算机系统中,您只能进行同类比较。a == b如果 a 和 b 属于完全相同的类型,则可以很容易地用精确的位和机器代码来表示确定真还是假的含义。当 a 和 b 是不同的东西时根本不清楚。同样的事情几乎适用于任何操作员;a + b,如果两者都是例如int,是一个清晰且易于理解的操作。但如果a是 achar并且b是 a double,那就根本不清楚了。
因此,在java中,所有涉及不同类型的二元运算符都是非法的。基本上,没有字节码可以直接比较 float 和 double,或者将字符串添加到 int。
但是,存在语法糖:当您编写a == b其中 a 和 b 是不同类型时,java确定两种类型之一是另一种类型的“子集”,那么 java 会简单地将“较小”类型转换为“较大”类型' 类型,这样操作就可以成功。例如:
int x = 5;
long y = 5;
System.out.println(x == y);
Run Code Online (Sandbox Code Playgroud)
这是可行的 - 因为 java 意识到将 an 转换int为long值永远不会失败,因此它不会打扰您显式指定您希望代码执行此操作。用 JLS 术语来说,这称为加宽转换。相反,任何将“较大”类型转换为“较小”类型的尝试都是不合法的,您必须显式强制转换:
long x = 5;
int y = x; // does not compile
int y = (int) x; // but this does.
Run Code Online (Sandbox Code Playgroud)
要点很简单:当你编写上面(int x = 5; long y = x;)的相反代码时,代码是相同的,只是编译器默默地(long)为你注入强制转换,前提是不会发生任何损失。同样的事情也发生在这里:
int x = 5;
long y = 10;
long z = x + y;
Run Code Online (Sandbox Code Playgroud)
它可以编译,因为 javac为您添加了一些语法糖,具体来说,它的编译就像它说的:long z = ((long) x) + y;。表达式的“类型”x + y是long。
关键技巧如下:Java 考虑将 int 转换为 float,以及将 int 或 long 转换为 double - 一种扩大转换。
例如,javac 只会假设它可以安全地做到这一点而没有任何损失,因此不会强制程序员通过手动添加强制转换来明确确认。然而, int->float 以及 long->double实际上并不完全安全。
浮点数可以表示 -2^23 到 +2^23 之间的每个整数值,双精度数可以表示 -2^52 到 +2^52 之间的每个整数值(source)。但int可以表示-2^31to+2^31-1和 longs -2^63to之间的每个整数值+2^63-1。这意味着在边缘(非常大的负/正数),存在可以用 int 表示但不能用 float 表示的整数值,或者可以用 long 表示但不能用 double 表示的整数值(幸运的是,所有 int 都可以用 double 表示;int -> double转换是完全安全的)。但java并不“承认”这一点,这意味着静默扩大转换仍然可以静默地丢弃数据(引入舍入)。
这就是这里发生的情况:(f == i)语法糖化(f == ((float) i))并且从 int 到 float 的转换引入了舍入。
大多数情况下,当使用双精度数和浮点数时,仍然希望获得精确的数字,你已经搞砸了。这些概念从根本上来说并不精确,并且这种精确性不能通过尝试考虑误差带来旁加载,因为无法跟踪由于 float 和 double 的舍入行为而引入的错误(无论如何不容易)。因此,您不应该使用 float/double。要么找到一个原子单位并用 int/long 表示它们,要么使用BigDecimal。(例如:要编写簿记软件,请勿将财务金额存储为double。请将它们存储为“美分”(或聪、日元或便士或该货币的任何原子单位)long,或者,BigDecimal如果您确实知道什么,请使用你正在做的事)。
如果您绝对肯定这里使用浮点(甚至双精度)是可以接受的,并且您仍然想要精确性,我们有一些解决方案。
选项 1 是利用 BigDecimal 的强大功能:
new BigDecimal(someDouble).intValueExact()
Run Code Online (Sandbox Code Playgroud)
这是可行的,是 100% 可靠的(除非浮点到双精度转换可以以某种方式将非精确值转换为精确值,我认为这不会发生),并且抛出。它也很慢。
另一种方法是利用我们对 IEEE 浮点标准工作原理的了解。
一个真正简单的答案是简单地运行您编写的算法,但要添加额外的检查:如果您int获得的值低于-2^23或高于+2^23,那么它可能不正确。然而,仍然有一些低于 -2^23 和 +2^23 的数字可以用 float 和 int 完美表示,只是不再是此时的每个数字。如果您想要一个也能接受这些确切数字的算法,那么它就会变得更加复杂。我的建议是不要深入研究那个污水池:如果你有一个过程,你最终得到的结果float接近于这样的极端,并且你想将它们转变为,int但前提是这可以不造成损失,那么你已经达到了一个疯狂的问题,你需要重新连接你得到的部件!
如果你真的需要这个,float我建议BigDecimal().intValueExact()你使用这个技巧,而不是试图对它进行数字运算。