【问题标题】:How to add two floating point numbers with opposite sign?如何将两个符号相反的浮点数相加?
【发布时间】:2019-10-14 22:46:58
【问题描述】:

为了好玩并想了解更多关于浮点数的工作原理,我正在尝试创建一个函数,该函数接受两个单精度浮点数并将它们相加。

到目前为止,我所做的对于相同符号的数字非常有效,但是当数字具有相反的符号时,它就会分崩离析。我查看了许多问题和网站(UAFHow do you add 8-bit floating point with different signsICLAdding 32 bit floating point numbers.How to add and subtract 16 bit floating point half precision numbers?How to subtract IEEE 754 numbers?),但那些提出减法的问题大多将其描述为“基本相同,但要减去”,我发现这不是很有帮助。 UAF 确实

通过首先转换为 2 的补码然后执行加法来处理负尾数。加法完成后,将结果转换回符号大小形式。

但我似乎不知道该怎么做。我发现 thisthis 解释了有符号幅度是什么以及如何在它和二进制补码之间进行转换,所以我尝试像这样进行转换:

manz = manx + ( ( (many | 0x01000000) ^ 0x007FFFFF) + 1);

像这样:

manz = manx + ( ( (many | 0x01000000) ^ 0x007FFFFF) + 1);
manz = ( ((manz - 1) ^ 0x007FFFFF) & 0xFEFFFFFF);

但这些都不起作用。

尝试了其他来源描述的减法方法,我尝试以如下各种方式否定负数的尾数:

manz = manx - many;
manz = manx + (many - (1<<23));
manz = manx + (many - (1<<24));
manz = manx + ( (many - (1<<23)) & 0x007FFFFF );
manz = manx + ( (many - (1<<23)) + 1);
manz = manx + ( (~many & 0x007FFFFF) + 1);
manz = manx + (~many + 1);
manz = manx + ( (many ^ 0x007FFFFF) + 1);
manz = manx + ( (many ^ 0x00FFFFFF) + 1);
manz = manx + ( (many ^ 0x003FFFFF) + 1);

这是应该根据符号处理加法的语句,它是在尾数对齐之后:

expz = expy;
if(signx != signy) { // opp sign
  if(manx < many) {
    signz = signy;
    manz = many + ((manx ^ 0x007FFFFF) + 1);
  } else if(manx > many) {
    signz = signx;
    manz = manx - ((many ^ 0x007FFFFF) + 1);
  } else { // x == y
    signz = 0x00000000;
    expz  = 0x00000000;
    manz  = 0x00000000;
  }
} else {
  signz = signx;
  manz  = manx + many;
}

这是紧随其后的代码,它在溢出的情况下对数字进行规范化,当它们具有相同的符号时它可以工作,但我不确定它在减法时的工作方式是否有意义:

if(manz & 0x01000000) {
  expz++;
  manz = (manz >> 1) + (manz & 0x1);
}
manz &= 0x007FFFFF;

使用测试值-3.34632F34.8532413F,当它应该是0x41FC0E2D (31.506922) 和测试值3.34632F 和@ 时,我得到了答案0x427E0716 (63.506920) 987654342@,当它应该是0xC1FC0E2D (-31.506922) 时,我得到了答案0xC27E0716 (-63.506920)。


我能够通过改变减法时对浮点数进行标准化的方式来解决我的问题。

expz = expy;
if(signx != signy) { // opp sign
  if(manx < many) {
    signz = signy;
    manz  = many - manx;
  } else if(manx > many) {
    signz = signx;
    manz  = manx - many;
  } else { // x == y
    signz = 0x00000000;
    expz  = 0x00000000;
    manz  = 0x00000000;
  }
  // Normalize subtraction
  while((manz & 0x00800000) == 0 && manz) {
      manz <<= 1;
      expz--;
  }
} else {
  signz = signx;
  manz  = manx + many;
  // Normalize addition
  if(manz & 0x01000000) {
    expz++;
    manz = (manz >> 1) + ( (x & 0x2) ? (x & 0x1) : 0 ); // round even
  }
}
manz &= 0x007FFFFF;

【问题讨论】:

  • printf("%f\n", -3.34632F + 34.8532413F); 打印 31.506922
  • 很抱歉,我真的看不出问题所在。如果您有两个浮点数 ab。为什么不直接使用a+b 来添加它们?
  • 因为没有显示如何加法的工作原理,我想准确地弄清楚加法是如何执行的,而不仅仅是添加两个浮点数。
  • @klutt 因为任务是通过了解存储格式来分解两个floats 并创建一个新的来做总和。
  • 也许这有帮助? stackoverflow.com/q/12146443/6699433

标签: c algorithm floating-point


【解决方案1】:

如何将两个符号相反的浮点数相加?

大多数情况下你不会。

对于不能依赖“溢出时二进制补码换行”的所有数字类型(例如浮点数、大数库等),您总是会得到类似的结果:

add_signed(v1, v2) {
    if( v1 < 0) {
        if( v2 < 0) {
            // Both negative
            return -add_unsigned(-v1, -v2);
        } else {
            // Different sign, v1 is negative
            return subtract_unsigned(v2, -v1);
        }
    } else {
        if( v2 < 0) {
            // Different sign, v2 is negative
            return subtract_unsigned(v1, -v2);
        } else {
            // Both positive
            return add_unsigned(v1, v2);
        }
    }
 }

subtract_signed(v1, v2) {
    return add_signed(v1, -v2);
}

add_unsigned(v1, v2) {
    // Here we know that v1 and v2 will never be negative, and
    //   we know that the result will never be negative
    ...
}

subtract_unsigned(v1, v2) {
    if(v1 < v2) {
        return -subtract_unsigned(v2, v1);
    }
    // Here we know that v1 and v2 will never be negative, and
    //   we know that the result will never be negative
    ...
}

换句话说;所有实际加法和所有实际减法都发生在无符号(“从不负”)数字中。

仅添加 32 位浮点仿真的更完整示例(在 C 语言中,未经测试且可能存在错误,可能或可能不适用于非规范化,不支持“NaN/s”或无穷大,不支持上溢或下溢, 没有“向左移动尾数以减少舍入前的精度损失”,并且不支持与“向零舍入”不同的舍入模式):

#define SIGN_FLAG      0x80000000U
#define EXPONENT_MASK  0x7F800000U
#define MANTISSA_MASK  0x007FFFFFU
#define IMPLIED_BIT    0x00800000U
#define OVERFLOW_BIT   0x01000000U
#define EXPONENT_ONE   0x00800000U

uint32_t add_signed(uint32_t v1, uint32_t v2) {
    if( (v1 & SIGN_FLAG) != 0) {
        if( (v2 & SIGN_FLAG) != 0) {
            // Both negative
            return SIGN_FLAG | add_unsigned(v1 & ~SIGN_FLAG, v2 & ~SIGN_FLAG);
        } else {
            // Different sign, v1 is negative
            return subtract_unsigned(v2, v1 & ~SIGN_FLAG);
        }
    } else {
        if( (v2 & SIGN_FLAG) != 0) {
            // Different sign, v2 is negative
            return subtract_unsigned(v1, v2 & ~SIGN_FLAG);
        } else {
            // Both positive
            return add_unsigned(v1, v2);
        }
    }
 }

uint32_t subtract_signed(uint32_t v1, uint32_t v2) {
    return add_signed(v1, v2 ^ SIGN_FLAG);
}

uint32_t add_unsigned(uint32_t v1, uint32_t v2) {
    // Here we know that v1 and v2 will never be negative, and
    //   we know that the result will never be negative

    if(v1 < v2) {    // WARNING: Compares both exponents and mantissas
        return add_unsigned(v2, v1);
    }

    // Here we know the exponent of v1 is not smaller than the exponent of v2

    uint32_t m1 = (v1 & MANTISSA_MASK) | IMPLIED_BIT;
    uint32_t m2 = (v2 & MANTISSA_MASK) | IMPLIED_BIT;
    uint32_t exp2 = v2 & EXPONENT_MASK;
    uint32_t expr = v1 & EXPONENT_MASK;

    while(exp2 < expr) {
        m2 >>= 1;
        exp2 += EXPONENT_ONE;
    }
    uint32_t mr = m1+m2;
    if( (mr & OVERFLOW_BIT) != 0) {
        mr >> 1;
        expr += EXPONENT_ONE;
    }
    return expr | (mr & ~IMPLIED_BIT);
}

uint32_t subtract_unsigned(uint32_t v1, uint32_t v2) {
    if(v1 == v2) {
        return 0;
    }
    if(v1 < v2) {
        return SIGN_FLAG ^ subtract_unsigned(v2, v1);
    }

    // Here we know the exponent of v1 is not smaller than the exponent of v2,
    //  and that (if exponents are equal) the mantissa of v1 is larger
    //  than the mantissa of v2; and therefore the result will be
    //  positive

    uint32_t m1 = (v1 & MANTISSA_MASK) | IMPLIED_BIT;
    uint32_t m2 = (v2 & MANTISSA_MASK) | IMPLIED_BIT;
    uint32_t exp2 = v2 & EXPONENT_MASK;
    uint32_t expr = v1 & EXPONENT_MASK;

    while(exp2 < expr) {
        m2 >>= 1;
        exp2 += EXPONENT_ONE;
    }
    uint32_t mr = m1-m2;
    while( (mr & IMPLIED_BIT) == 0) {
        mr <<= 1;
        expr -= EXPONENT_ONE;
    }
    return expr | (mr & ~IMPLIED_BIT);
}

【讨论】:

  • 我不太确定你的意思是什么,我不认为你的意思是使用“无符号浮点数”或类似的东西,但我不确定这将如何应用于我的特殊问题。
  • 我的意思是;对于add_unsigned(v1, v2),结果的值v1v2 永远不会是负数;对于sub_unsigned(v1,v2)v1v2 的值永远不会为负(但如果v1 小于v2,则结果可能为负,这是您要在函数中测试的特殊情况) .
  • 更新了示例代码以显示逻辑(并表明,在真正重要的地方,v1v2 并且结果永远不会是负数)。
  • 我在相关代码中所做的是,如果浮点数具有相反的符号,则使结果符号与较大幅度的浮点数相同,然后减去浮点数的尾数与较大量级的尾数相比较小的量级,但我一直无法准确地弄清楚如何实际减去它们。
  • 对于浮点数;在丢弃/忽略符号标志后,确保从较大的值中减去较小的值并确保指数匹配,这只是尾数的直接减法(非常类似于您已经在做的“直接添加尾数”其他情况)。
猜你喜欢
  • 1970-01-01
  • 2022-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多