精度损失 - int -> float或double

29

我正在复习一道考试题,这道题的分值为4分。

"在Java中,我们可以将int赋值给double或float。" 这样做是否会丢失信息?为什么?

我这样写是因为int通常是固定长度或大小的 - 存储数据的精度是有限的,而在浮点数中存储信息可以是无限的,基本上是由于这个原因我们会丢失信息。

现在我对自己是否到达了正确的研究领域有些疑惑。 我很确定它会失去精度,但我确切地说不清楚原因。 能否请您提供一些帮助呢?


3
你从哪里得到浮点数是无穷大的想法? - Paul Tomblin
它们的精度可以被修改,对吧? - stan
7
当然,floatdouble并没有无限精度。如果是这样的话,它们就会成为神奇的东西,在有限的内存空间中存储无限量的信息。 - Jesper
1
不行,至少对于Java中的浮点数来说是不行的。当然,对于任意精度算术,你可以使用BigDecimal类。 - Michael Borgwardt
1
将一个 int 存储到之前没有存储任何有用信息的 float 中,然后丢弃或覆盖 int 可能会丢失信息,但是造成信息丢失的是丢弃或覆盖 int 的行为,而不是存储 float。相反,覆盖任何类型的任何变量,该变量保存了唯一的有用信息副本,即使将变量设置为存储另一个变量中信息的完美副本,也可能导致信息丢失。 - supercat
9个回答

59

在Java中,Integer使用32位来表示其值。

在Java中,FLOAT使用23位的尾数,因此大于2 ^ 23的整数将被截断其最不重要的位。例如,33554435(或0x200003)将被截断为约33554432 +/- 4。

在Java中,DOUBLE使用52位的尾数,因此能够表示32位整数而不会丢失数据。

另请参见维基百科上的“浮点数


12
浮点数和双精度浮点数的尾数实际上分别为24位和53位。只是因为最高位始终为1,所以它不需要存储在表示中。 - slacker

28

了解浮点数的内部布局并不是必要的。你只需要掌握抽屉原理和intfloat类型具有相同的大小。

  • int是32位类型,其每个比特模式表示一个不同的整数,因此有2^32种int值。
  • float是32位类型,因此它最多有2^32个不同的值。
  • 一些float表示非整数,因此表示整数的float少于 2^32。
  • 因此,不同的int值将被转换为相同的float(即丢失精度)。

类似的推理可以用于longdouble


21

以下是JLS关于该问题的非技术性讨论。

JLS 5.1.2 扩展原始类型转换

原始类型上的19种特定转换称为扩展原始类型转换:

  • intlongfloatdouble
  • (其他省略)

intlong 值转换为 float,或将 long 值转换为 double,可能会导致精度损失 -- 即结果可能会失去一些最低有效位的值。在这种情况下,使用IEEE 754四舍五入到最近模式对整数值进行正确舍入后,所得到的浮点数值将是正确的。

尽管可能会发生精度损失,但原始类型之间的扩展转换永远不会导致运行时异常。

以下是一个会丢失精度的扩展转换示例:

class Test {
         public static void main(String[] args) {
                int big = 1234567890;
                float approx = big;
                System.out.println(big - (int)approx);
        }
}

输出以下内容:

-46

因此表明在从类型为int的值转换为类型为float的值时丢失了信息,因为类型为float的值不精确到九个有效数字。


15
不,'float'和'double'也是固定长度的,只是它们使用它们的位不同。在Floating-Point Guide中可以详细了解它们的工作原理。
基本上,将'int'赋值给'double'时,您不会失去精度,因为'double'具有52位精度,足以容纳所有'int'值。但'float'只有23位精度,因此无法精确表示所有大于约2^23的'int'值。

3
与其说它无法容纳大于2^23的int值,更精确的说法是连续可表示整数的范围正好从-2^24到+2^24。这些值以及它们之间的所有值都可以用float表示;而超出该范围的最接近整数则不能。 - supercat

9
您的直觉是正确的,将int转换为float时可能会失去精度。但是,这并不像大多数其他答案中所述那样简单。

在Java中,FLOAT使用23位尾数,因此大于2^23的整数将截断其最低有效位。(摘自本页上的帖子)

不是真的。
例如:下面是一个大于2^23的整数,将其转换为浮点数而不丢失任何信息:
int i = 33_554_430 * 64; // is greater than 2^23 (and also greater than 2^24); i = 2_147_483_520
float f = i;
System.out.println("result: " + (i - (int) f)); // Prints: result: 0
System.out.println("with i:" + i + ",  f:" + f);//Prints: with i:2_147_483_520,  f:2.14748352E9

因此,大于2^23的整数不会被截断其最低有效位。
最好的解释在这里:
Java中的float是32位的,表示为:
符号 * 尾数 * 2^指数
符号 * (0到33_554_431) * 2^(-125到+127)
来源:http://www.ibm.com/developerworks/java/library/j-math2/index.html 为什么这是个问题?
它给人留下了这样的印象:您可以通过查看int的大小来确定是否存在从int到float的精度损失。
我特别看到过Java考试题,其中一个问题是问一个大的int是否转换为float而不会有任何损失。
此外,有时人们倾向于认为从int到float会有精度损失:
当int大于:1_234_567_890 不正确(请参见上面的反例)
当int大于:2的23次方 (等于:8_388_608) 不正确
当int大于:2的24次方 (等于:16_777_216) 不正确
结论
从足够大的int到float的转换可能会失去精度。
不能仅通过查看int的大小(即不尝试深入实际的float表示)来确定是否会有损失。

8
在范围为-2^24到+2^24的所有整数都可以表示为“float”,同样在范围为-2^25到+2^25的所有偶数整数,范围为-2^26到+2^26的所有四的倍数等也都可以表示为“float”。要快速确定一个整数是否可以无损转换,可以将其除以20,000,000,取不小于该值的最小二次幂,然后再将该值除以该二次幂,如果结果超过16,777,216,则再除以2。如果二次幂除法或最终的除以2操作产生小数,则该数字不能表示为“float”。 - supercat
大于2^24的奇数总是会失去精度。而不被4整除的大于2^25的数字也是如此,等等... - Djeefther Souza

4
可能是我见过的最清晰的解释: http://www.ibm.com/developerworks/java/library/j-math2/index.html ULP或最小精度单位定义了任意两个浮点值之间可用的精度。随着这些值的增加,可用精度会降低。 例如:在1.0和2.0之间(包括1.0和2.0)有8,388,609个浮点数,在1,000,000和1,000,001之间只有17个。在10,000,000处,ULP为1.0,因此在此值以上,很快就会有多个整数值映射到每个可用的浮点数,从而导致精度损失。

1

将int赋值给double或float可能会丢失精度,原因有两个:

  • 有些数字无法用double/float表示,因此它们最终被近似
  • 大整数可能在最低有效位包含过多的精度

1
只有你的第二点与int值相关。所有数学整数都可以用足够长度的二进制浮点格式精确表示。(64位)Java double格式对于所有(32位)Java int值具有足够的长度,但(32位)Java float格式没有,这会导致最不重要的数字四舍五入。你的第一点只适用于分数,而不是整数。 - james turner

0

对于这些示例,我使用Java。

使用类似以下的函数来检查从int到float转换时是否存在精度损失

static boolean checkPrecisionLossToFloat(int val)
{
  if(val < 0)
  {
    val = -val;
  }
  // 8 is the bit-width of the exponent for single-precision
  return Integer.numberOfLeadingZeros(val) + Integer.numberOfTrailingZeros(val) < 8;
}

使用类似这样的函数来检查从 long 转换为 double 时是否存在精度损失。
static boolean checkPrecisionLossToDouble(long val)
{
  if(val < 0)
  {
    val = -val;
  }
  // 11 is the bit-width for the exponent in double-precision
  return Long.numberOfLeadingZeros(val) + Long.numberOfTrailingZeros(val) < 11;
}

使用类似这样的函数,在从长整型转换为浮点数时检查精度损失。
static boolean checkPrecisionLossToFloat(long val)
{
  if(val < 0)
  {
    val = -val;
  }
  // 8 + 32
  return Long.numberOfLeadingZeros(val) + Long.numberOfTrailingZeros(val) < 40;
}

对于这些函数中的每一个,返回 true 意味着将整数值转换为浮点值会导致精度损失。
如果整数值具有超过 24 个有效位,则转换为浮点数将丢失精度。
如果整数值具有超过 53 个有效位,则转换为双精度浮点数将丢失精度。

-3

你可以将double赋值给int而不会失去精度。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接