19

变量的每个可能值都可以在float变量中精确表示double吗?

换句话说,对于所有可能的值X,以下将是成功的:

float f1 = X;
double d = f1;
float f2 = (float)d;

if(f1 == f2)
  System.out.println("Success!");
else
  System.out.println("Failure!");

我的怀疑是没有例外,或者如果有它只是一个边缘情况(如 +/- 无穷大或 NaN)。

编辑:问题的原始措辞令人困惑(陈述了两种方式,一种会回答“否”,另一种会回答“是”以获得相同的答案)。我已对其进行了改写,使其与问题标题相匹配。

4

11 回答 11

26

是的。

通过列举所有可能的情况来证明:

public class TestDoubleFloat  {
    public static void main(String[] args) {
        for (long i = Integer.MIN_VALUE; i <= Integer.MAX_VALUE; i++) {
            float f1 = Float.intBitsToFloat((int) i);
            double d = (double) f1;
            float f2 = (float) d;
            if (f1 != f2) {
                if (Float.isNaN(f1) && Float.isNaN(f2)) {
                    continue; // ok, NaN
                }
                fail("oops: " + f1 + " != " + f2);
            }
        }
    }
}

在我的机器上在 12 秒内完成。32 位很小

于 2008-11-03T18:27:44.590 回答
5

理论上,没有这样的值,所以“是的”,每个浮点数都应该可以表示为双精度浮点数。从浮点数转换为双精度数应该只涉及最后添加四个字节的 00——它们使用相同的格式,只是具有不同大小的字段。

于 2008-11-03T15:42:59.273 回答
5

是的,浮点数是双精度数的子集。浮点数和双精度数都具有 (sign * a * 2^b) 形式。浮点数和双精度数的区别在于 a 和 b 中的位数。由于双精度数有更多可用位,因此将浮点值分配给双精度数实际上意味着插入额外的 0 位。

于 2008-11-03T15:43:01.647 回答
3

正如每个人都已经说过的那样,“不”。但这实际上是对问题本身的“是”,即每个浮点数可以精确地表示为双精度数。令人困惑。:)

于 2008-11-03T15:45:49.970 回答
3

如果我正确阅读了语言规范(并且其他所有人都在确认),那么就没有这样的价值。

也就是说,每个都声称只保留 IEEE 754 标准值,因此两者之间的转换应该不会引起任何变化,除非在给定的内存中。

(澄清:只要该值小到可以保存在浮点数中,就不会发生变化;显然,如果该值的位数太多而无法保存在浮点数中,那么从双精度数转换为浮点数会导致精度损失。)

于 2008-11-03T15:50:39.430 回答
1

@KenG:这段代码:

float a = 0.1F
println "a=${a}"
double d = a
println "d=${d}"

失败不是因为 0.1f 不能准确表示。问题是“是否有一个不能表示为双精度的浮点值”,这段代码没有证明这一点。虽然 0.1f 不能精确存储,但给定的值(不完全是 0.1f)可以存储为双精度(也不完全是 0.1f)。假设一个 Intel FPU,a 的位模式是:

0 01111011 10011001100110011001101

d 的位模式是:

0 01111111011 100110011001100110011010 (后跟更多的零)

它具有相同的符号、指数(两种情况下均为-4)和相同的小数部分(上面用空格分隔)。输出的差异是由于数字中第二个非零数字的位置(第一个是点之后的 1)只能用双精度表示。输出字符串格式的代码将中间值存储在内存中,并且特定于浮点数和双精度数(即有一个函数双精度字符串和另一个浮点数字符串)。如果 to-string 函数被优化为使用 FPU 堆栈来存储 to-string 过程的中间结果,那么 float 和 double 的输出将是相同的,因为 FPU 对 float 使用相同的更大格式(80 位)和双。

没有浮点值不能相同地存储在双精度值中,即浮点值集是双精度值集的子集。

于 2008-11-03T17:28:31.823 回答
0

Snark: NaN s 在转换之后(或实际上之前)会进行不同的比较。

然而,这并不会使已经给出的答案无效。

于 2008-11-03T16:10:20.063 回答
0

我采用了您列出的代码并决定在 C++ 中尝试它,因为我认为它可能会执行得更快一些,并且执行不安全的强制转换要容易得多。:-D

我发现对于有效数字,转换是有效的,并且在转换后你会得到精确的按位表示。但是,对于非数字,例如 1.#QNAN0 等,结果将使用非数字的简化表示,而不是源的确切位。例如:

**** 失败**** 2140188725 | 1.#QNAN0 -- 0xa0000000 0x7ffa1606

我将一个无符号整数转换为浮点数,然后加倍并返回浮点数。数字 2140188725 (0x7F90B035) 产生 NAN,转换为 double 和 back 仍然是 NAN,但不是完全相同的NAN。

这是简单的 C++ 代码:

typedef unsigned int uint;
for (uint i = 0; i < 0xFFFFFFFF; ++i)
{
    float f1 = *(float *)&i;
    double d = f1;
    float f2 = (float)d;
    if(f1 != f2)
        printf("**** FAILURE **** %u | %f -- 0x%08x 0x%08x\n", i, f1, f1, f2);
    if ((i % 1000000) == 0)
        printf("Iteration: %d\n", i);
}
于 2008-11-03T16:15:19.833 回答
0

第一个问题的答案是肯定的,“换句话说”的答案是否定的。如果您将代码中的测试更改为if (!(f1 != f2))第二个问题的答案变为“是”——它将为所有浮点值打印“成功”。

于 2008-11-04T00:36:42.100 回答
0

理论上,每个普通单曲都可以填充指数和尾数以创建双精度,然后删除填充,然后返回原始单曲。

当你从理论走向现实时,你就会遇到问题。我不知道您是否对理论或实现感兴趣。如果是实施,那么您很快就会遇到麻烦。

IEEE 是一种可怕的格式,我的理解是,它被故意设计得如此艰难,以至于没有人能够满足它,并让市场赶上英特尔(这是不久前的),从而允许更多的竞争。如果这是真的,它失败了,无论哪种方式,我们都被这个可怕的规范所困扰。像 TI 格式这样的东西在很多方面都比现实世界优越得多。我与任何一家公司或任何这些格式都没有联系。

多亏了这个规范,实际上满足它的 fpus 很少(在硬件中,甚至在硬件和操作系统中),而且那些在下一代经常失败的 fpus。(谷歌:TestFloat)。这些天的问题往往在于 int 浮动和浮动到 int,而不是像您上面指定的那样单到双和双到单。当然,fpu 将执行什么操作来进行转换?加0?乘以 1?取决于 fpu 和编译器。

与上述问题相关的 IEEE 的问题是,数字的方式不止一种,不是每个数字都可以表示,而是可以表示许多数字。如果我想破坏您的代码,我将从负零开始,希望两个操作之一将其转换为正零。然后我会尝试非规范化。它应该失败并带有信号 nan,但是您将其称为已知异常。

问题是等号,这里是关于浮点的第一条规则,永远不要使用等号。等于是位比较而不是值比较,如果您有两个以不同方式表示的值(例如加零和负零),即使它的数字相同,位比较也会失败。大于和小于在 fpu 中完成,等于在整数 alu 中完成。

我意识到您可能使用等于来解释问题,而不一定是您想要成功或失败的代码。

于 2008-11-04T15:48:13.227 回答
0

如果将浮点类型视为表示精确值,那么正如其他发布者所指出的那样,每个float值都可以表示为 a double,但只有少数 的值double可以表示为float。另一方面,如果认识到浮点值是近似值,就会意识到实际情况正好相反。如果使用非常精确的仪器来测量 3.437 毫米的东西,那么可以正确地描述为 3.4 毫米。如果用尺子将物体测量为 3.4mm,则将其大小描述为 3.400mm 是不正确的。

范围的顶部存在更大的问题。有一个float值表示:“计算值超出 2^127 未知量”,但没有double值表示这种情况。将“无穷大”从单数转换为双数将产生一个值“计算值超出 2^1023 未知数量”,该值相差超过一个 googol。

于 2012-08-28T03:49:53.433 回答