每个浮点数都可以完全表示为双精度数吗?

时间:2008-11-03 15:39:41

标签: java floating-point

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)。

编辑:问题的原始措辞令人困惑(说明两种方式,一种是“否”,另一种则回答“是”同一答案)。我已经对它进行了重写,以便与问题标题相匹配。

11 个答案:

答案 0 :(得分:23)

通过列举所有可能的案例证明:

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位

答案 1 :(得分:5)

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

答案 2 :(得分:4)

是的,花车是双打的子集。浮动和双打都有形式(符号* a * 2 ^ b)。浮点数和双精度数之间的差异是&amp;和/或湾由于双精度位数可用,因此有效地将浮点值赋值为double意味着插入额外的0位。

答案 3 :(得分:3)

正如大家已经说过的那样,“不”。但这对问题本身来说实际上是“是”,即每个浮点数可以完全表示为双精度。混乱。 :)

答案 4 :(得分:3)

如果我正确地阅读了language specification(正如其他所有人都在确认的那样),那就没有这样的价值了。

也就是说,每个人声称只保留IEEE 754标准值,因此两者之间的演员阵容应该不会发生变化,除非是在给定的记忆中。

(澄清:只要该值足够小以便保存在浮点数中,就不会有任何变化;显然,如果该值太多而无法在浮点数中保留,则从double转换为float将会导致精度下降。)

答案 5 :(得分:1)

@KenG:此代码:

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

失败不是因为0.1f无法准确表示。问题是“是否存在一个不能表示为double的浮点值”,此代码无法证明。虽然0.1f不能精确存储,但给出的a值(精确度不是0.1f)可以存储为double(也不会精确到0.1f)。假设是英特尔FPU,则a的位模式为:

  

0 01111011 10011001100110011001101

并且d的位模式是:

  

0 01111111011 100110011001100110011010(其次是更多的零)

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

没有浮点值不能以相同的方式存储在double中,即浮点值集是double值集的子集。

答案 6 :(得分:0)

Snark: NaN将在转换之后(或之前)进行不同的比较。

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

答案 7 :(得分:0)

我选择了你列出的代码并决定在C ++中尝试它,因为我认为它可能会执行得更快一些,并且更容易进行不安全的转换。 :-D

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

  

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

我将一个unsigned int转换为float,然后转换为double并返回float。数字2140188725(0x7F90B035)导致NAN并且转换为double并且返回仍然是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);
}

答案 8 :(得分:0)

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

答案 9 :(得分:0)

理论上,每个普通单个都可以使用指数和尾数填充来创建一个double,然后删除填充,然后返回原始单个。

当你从理论走向现实时,你会遇到问题。我不知道你是否对理论或实施感兴趣。如果它是实施,那么你可以迅速陷入困境。

IEEE是一种可怕的格式,我的理解是它故意设计得如此强硬以至于没有人能够满足它并允许市场赶上英特尔(这是一段时间之后)允许更多的竞争。如果这是真的失败了,无论哪种方式我们都坚持这个可怕的规范。像TI这样的东西在很多方面远远优于现实世界。我与公司或任何这些格式没有关系。

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

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

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

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

答案 10 :(得分:0)

如果浮点类型被视为表示精确值,那么正如其他海报所指出的那样,每个float值都可以表示为double,但只有{{1}的值} {}可以由double表示。另一方面,如果人们认识到浮点值是近似值,那么人们就会意识到真实情况是相反的。如果使用非常精确的仪器测量3.437mm的东西,可以正确描述尺寸为3.4mm。如果使用标尺将物体测量为3.4mm,则将其尺寸描述为3.400mm是不正确的。

在该范围的顶部存在更大的问题。有一个float值表示:“计算值超过2 ^ 127一个未知数量”,但没有float值表示这样的事情。将“无穷大”从单个变为双精度将产生“计算值超过2 ^ 1023未知量”的值,该值超过googol。