代码之家  ›  专栏  ›  技术社区  ›  gzh

将无符号int强制转换为float时出错

c
  •  2
  • gzh  · 技术社区  · 7 年前

    对于以下程序。

    #include <stdio.h>
    
    int main()
    {
        unsigned int a = 10;
        unsigned int b = 20;
        unsigned int c = 30;
        float d = -((a*b)*(c/3));
        printf("d = %f\n", d);
        return 0;
    }
    

    很奇怪输出是

    d = 4294965248.000000
    

    当我改变魔法数字时 3 在表达式中计算d到 3.0 ,我得到了正确的结果:

    d = 2000.000000
    

    如果我把A,B,C的类型改成 int ,我也得到了正确的结果。

    我想这个错误是由 unsigned int float ,但我不知道这个奇怪结果是如何产生的。

    6 回复  |  直到 7 年前
        1
  •  2
  •   Soner from The Ottoman Empire    7 年前

    我想你应该意识到你把- unsigned int 转让前 float . 如果运行下面的代码,您将很可能 4294965296

    #include <stdio.h>
    
    int main()
    {
        unsigned int a = 10;
        unsigned int b = 20;
        unsigned int c = 30;
        printf("%u", -((a*b)*(c/3)));
    
        return 0;
    }
    

    这个 -2000 在等号的右边设置为有符号 整数(大小可能为32位),并具有十六进制值 0xFFFFF830 . 编译器生成代码来移动这个有符号整数 变成你的无符号整数 x 它也是一个32位实体。这个 编译器假定您只有 等于号,所以它只需将所有32位移到 X . X 现在有 价值 0xFFFF830 哪个是 四十二亿九千四百九十六万五千二百九十六 如果解释为阳性 号码。但是 printf 格式 %d 说32位是 解释为有符号整数,因此 - 2000 . 如果你用过 %u 它会打印成 四十二亿九千四百九十六万五千二百九十六 .

    #include <stdio.h>
    #include <limits.h>
    
    int main()
    {
        float d = 4294965296;
        printf("d = %f\n\n", d);
        return 0;
    }
    

    当你转换 四十二亿九千四百九十六万五千二百九十六 浮动 ,您使用的数字太长,无法与分数部分匹配。现在已经失去了一些精确性。因为损失,你得到了 4294965248.000000 正如我得到的。

    ieee-754浮点标准是表示 以及处理所有跟在后面的浮点量 现代计算机系统。

    bit  31 30    23 22                    0
         S  EEEEEEEE MMMMMMMMMMMMMMMMMMMMMMM
    

    位数是从最低有效位开始计算的。第一 比特是标志( 0 积极的一面, 1 表示否定)。以下 8 位是中的指数 EXCESS-127 二进制符号;这个 意味着二进制模式 01111111 = 127 表示指数 属于 , 1000000 = 128 ,表示1, 01111110 = 126 代表 -1 ,等等。尾数与其余部分相符 24 比特,与 其领先 如上所述剥去。 Source

    如您所见,在进行转换时 四十二亿九千四百九十六万五千二百九十六 浮动 ,精度为 00011000 损失发生。

    11111111111111111111100 00011000 0  <-- 4294965296
    11111111111111111111100 00000000 0  <-- 4294965248
    
        2
  •  1
  •   Conradin    7 年前

    这是因为你用 - 关于一个 unsigned int . 这个 - 反转数字的位。允许打印一些无符号整数:

    printf("Positive: %u\n", 2000);
    printf("Negative: %u\n", -2000);
    
    // Output:
    // Positive: 2000
    // Negative: 4294965296
    

    用于打印十六进制值:

    printf("Positive: %x\n", 2000);
    printf("Negative: %x\n", -2000);
    
    // Output
    // Positive: 7d0
    // Negative: fffff830
    

    如你所见,这些位是颠倒的。所以问题在于 - 无符号整型 ,不是铸造的 无符号整型 漂浮。

        3
  •  1
  •   Christian Gibbons    7 年前

    正如其他人所说,问题是你试图否定一个无符号的数字。已经给出的大多数解决方案都需要进行某种形式的浮点运算,以便在浮点类型上完成运算。另一种解决方法是将计算结果强制转换为 int 然后取反,这样算术运算就可以在整数类型上完成,这可能更好,也可能不更好,这取决于您的实际用例:

    #include <stdio.h>
    
    int main(void)
    {
        unsigned int a = 10;
        unsigned int b = 20;
        unsigned int c = 30;
        float d = -(int)((a*b)*(c/3));
        printf("d = %f\n", d);
        return 0;
    }
    
        4
  •  1
  •   Kami Kaze    7 年前

    你的全部计算将完成 unsigned 所以它和

     float d = -(2000u);
    

    -2000 在里面 unsigned int (假设32位 int 4294965295

    这写在你的 float d . 但作为 float 无法保存此确切数字 4294965248 .

    根据经验,可以说float的精度是7个有效的基数10位数。

    计算的是 2^32 - 2000 然后浮点精度做剩下的。


    如果你用 3.0 这将更改计算中的类型,如下所示

    float d = -((a*b)*(c/3.0));
    float d = -((unsigned*unsigned)*(unsigned/double));
    float d = -((unsigned)*(double));
    float d = -(double);
    

    给你留下正确的负值。

        5
  •  0
  •   Bing Bang    7 年前

    你需要把整数转换成浮点数

     float d = -((a*b)*(c/3));
    

    float d = -(((float)a*(float)b)*((float)c/3.0));
    
        6
  •  0
  •   Bathsheba    7 年前

    -((a*b)*(c/3)); 都是在 无符号整数运算 , 包括 一元否定。对于无符号类型,一元求反是定义良好的:从数学上讲,结果是模2 其中n是 unsigned int . 当你把那个大数字分配给 float ,您会遇到一些精度损失;由于其二进制大小,结果是最接近 无符号整型 那就是2048年。

    如果将3更改为3.0,则 c / 3.0 是一个 double 类型和结果 a * b 因此转换为 双重的 在被复制之前。这个 双重的 然后分配给 浮动 ,已观察到精度损失。