【问题标题】:How do I use bitwise operators on a "double" on C++?如何在 C++ 上的“double”上使用位运算符?
【发布时间】:2019-08-20 09:55:27
【问题描述】:

我被要求在 C 中获取不同类型的内部二进制表示。我的程序目前可以很好地与“int”一起使用,但我想将它与“double”和“float”一起使用。我的代码如下所示:

template <typename T>
string findBin(T x) {
string binary;
for(int i = 4096 ; i >= 1; i/=2) {
        if((x & i) != 0) binary += "1";
        else binary += "0";
}
    return binary;
}

当我尝试使用“double”或“float”实例化模板时程序失败。

【问题讨论】:

  • 使用双精度和/或浮点数时,您期望什么输出?
  • @Nathan:如果该位为 1,则为 0,如果该位为 0,则为 0。
  • 只是我认为您可能会觉得有趣的东西:某些系统(尤其是 Linux)有一个带有联合 ieee754_floatieee754_double 的 ieee754.h 标头,用于定义(字节序安全)位域以读取指数、尾数、符号位(负)、非数字位等

标签: c++ bitwise-operators


【解决方案1】:

简而言之,你没有。

位运算符在应用于doublefloat 时没有意义,标准规定位运算符(~&amp;|^&gt;&gt;&lt;&lt; 和赋值变体)不接受 doublefloat 操作数。

doublefloat 都有 3 个部分 - 符号位、指数和尾数。假设您可以将double 向右移动。尤其是指数,意味着没有简单的转换来向右移动位模式 - 符号位将移入指数,而指数的最低有效位将移入尾数,完全不明显的集合意义。在 IEEE 754 中,实际尾数位前面有一个隐含的 1 位,这也使解释复杂化。

类似的 cmets 适用于任何其他位运算符。

因此,由于对double 值的位运算符没有合理或有用的解释,因此标准不允许它们。


来自cmets:

我只对二进制表示感兴趣。我只想打印它,不做任何有用的事情。

这段代码是几年前为 SPARC(大端)架构编写的。

#include <stdio.h>

union u_double
{
    double  dbl;
    char    data[sizeof(double)];
};

union u_float
{
    float   flt;
    char    data[sizeof(float)];
};

static void dump_float(union u_float f)
{
    int exp;
    long mant;

    printf("32-bit float: sign: %d, ", (f.data[0] & 0x80) >> 7);
    exp = ((f.data[0] & 0x7F) << 1) | ((f.data[1] & 0x80) >> 7);
    printf("expt: %4d (unbiassed %5d), ", exp, exp - 127);
    mant = ((((f.data[1] & 0x7F) << 8) | (f.data[2] & 0xFF)) << 8) | (f.data[3] & 0xFF);
    printf("mant: %16ld (0x%06lX)\n", mant, mant);
}

static void dump_double(union u_double d)
{
    int exp;
    long long mant;

    printf("64-bit float: sign: %d, ", (d.data[0] & 0x80) >> 7);
    exp = ((d.data[0] & 0x7F) << 4) | ((d.data[1] & 0xF0) >> 4);
    printf("expt: %4d (unbiassed %5d), ", exp, exp - 1023);
    mant = ((((d.data[1] & 0x0F) << 8) | (d.data[2] & 0xFF)) << 8) | (d.data[3] & 0xFF);
    mant = (mant << 32) | ((((((d.data[4] & 0xFF) << 8) | (d.data[5] & 0xFF)) << 8) | (d.data[6] & 0xFF)) << 8) | (d.data[7] & 0xFF);
    printf("mant: %16lld (0x%013llX)\n", mant, mant);
}

static void print_value(double v)
{
    union u_double d;
    union u_float  f;

    f.flt = v;
    d.dbl = v;

    printf("SPARC: float/double of %g\n", v);
//    image_print(stdout, 0, f.data, sizeof(f.data));
//    image_print(stdout, 0, d.data, sizeof(d.data));
    dump_float(f);
    dump_double(d);
}


int main(void)
{
    print_value(+1.0);
    print_value(+2.0);
    print_value(+3.0);
    print_value( 0.0);
    print_value(-3.0);
    print_value(+3.1415926535897932);
    print_value(+1e126);
    return(0);
}

注释掉的“image_print()”函数以十六进制打印任意字节集,并进行了各种细微调整。如果您需要代码,请与我联系(请参阅我的个人资料)。

如果您使用的是 Intel(小端),您可能需要调整代码以处理反向位顺序。但它显示了您如何做到这一点 - 使用 union

【讨论】:

  • 我了解 double 和 float 在位级别的结构,但我只对二进制表示感兴趣。我只想打印它,不做任何有用的事情。
  • "...对于双值的位运算符没有合理或有用的解释..." False. 否决。
  • 简单。你是谁说检查或使用双值中的位没有理智或有用的目的?好像您知道所有可能的软件中双精度和位的所有可能用法,以便做出这样的陈述。
  • 这不是重点。重点是关于你的陈述,这是错误的。
  • 您的回答证明没有提供此类功能因为没有合理或有用的目的来使用它。 AnT 充分解释了他的答案中(或至少应该)缺少此功能的真正原因。你的意见是错误的,就像大多数人在提出案件后使用“同意不同意”和“只是我的意见”这样的短语。
【解决方案2】:

您不能直接将位运算符应用于floatdouble,但您仍然可以通过将变量放入具有适当大小的字符数组的union 中间接访问位,然后从这些位中读取位人物。例如:

string BitsFromDouble(double value) {
    union {
        double doubleValue;
        char   asChars[sizeof(double)];
    };

    doubleValue = value; // Write to the union

    /* Extract the bits. */
    string result;
    for (size i = 0; i < sizeof(double); ++i)
        result += CharToBits(asChars[i]);
    return result;
}

您可能需要调整您的例程以处理字符,字符的范围通常不超过 4096,并且这里的字节序可能也有些奇怪,但基本想法应该可行。它不会跨平台兼容,因为机器使用不同的字节顺序和双精度表示,所以要小心你如何使用它。

【讨论】:

    【解决方案3】:

    位运算符通常不适用于 any 类型的“二进制表示”(也称为 对象表示)。位运算符使用类型的值表示,这通常与对象表示不同。这适用于int 以及double

    如果您真的想获得任何类型对象的内部二进制表示,正如您在问题中所述,您需要将该类型的对象重新解释为 unsigned char 对象的数组,然后使用按位这些unsigned chars 上的运算符

    例如

    double d = 12.34;
    const unsigned char *c = reinterpret_cast<unsigned char *>(&d);
    

    现在通过访问元素c[0]c[sizeof(double) - 1],您将看到double 类型的内部表示。如果您愿意,可以对这些 unsigned char 值使用按位运算。

    再次注意,在一般情况下,为了访问int 类型的内部表示,您必须做同样的事情。它通常适用于char 类型以外的任何 类型。

    【讨论】:

      【解决方案4】:

      对指向 long long * 的双精度指针进行按位转换并取消引用。 示例:

      inline double bit_and_d(double* d, long long mask) {
        long long t = (*(long long*)d) & mask;
        return *(double*)&t;
      }
      

      编辑:这几乎肯定会与 gcc 对严格别名的执行相冲突。为此使用各种解决方法之一。 (memcpy、工会、__attribute__((__may_alias__)) 等)

      【讨论】:

      • 哎呀。我需要看看我总结的内容 =/
      • @Andrew sizeof(double) 为 8。即 64b。不知道你从哪里得到 16B。
      【解决方案5】:

      另一种解决方法是获取浮点变量的指针,并将其转换为相同大小的整数类型的指针,然后获取该指针指向的整数的值。现在您有了一个与浮点数具有相同二进制表示的整数变量,您可以使用按位运算符。

      string findBin(float f) {
          string binary;
          for(long i = 4096 ; i >= 1; i/=2) {
              long x = * ( long * ) &y;
              if((x & i) != 0) binary += "1";
              else binary += "0";
          }
          return binary;
      }
      

      但请记住:您必须转换为具有相同大小的类型。否则可能会发生不可预知的事情(如缓冲区溢出、访问冲突等)。

      【讨论】:

      • 我喜欢这个解决方案。谢谢。
      • 更好:long x = * reinterpret_cast (& y);
      【解决方案6】:

      正如其他人所说,您可以通过将double* 转换为long long*(或有时只是long*)对双精度数使用位运算符。

      int main(){
          double * x = (double*)malloc(sizeof(double));
          *x = -5.12345;
          printf("%f\n", *x);
          *((long*)x) &= 0x7FFFFFFFFFFFFFFF;
          printf("%f\n", *x);
          return 0;
      }
      

      在我的电脑上,这段代码打印出来:

      -5.123450
      5.123450
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-11-09
        • 2010-10-06
        • 2020-12-11
        • 1970-01-01
        • 2013-02-04
        • 2011-05-30
        • 2015-06-11
        相关资源
        最近更新 更多