Как добавить два числа с плавающей запятой с противоположным знаком? - PullRequest
1 голос
/ 15 октября 2019

Для развлечения и чтобы узнать больше о том, как работают поплавки, я пытаюсь создать функцию, которая берет два поплавка одинарной точности и складывает их вместе.

То, что я сделал до сих пор, работает отличнодля тех же чисел знака, но он разваливается, когда числа имеют противоположные знаки. Я просмотрел несколько вопросов и сайтов ( UAF , Как добавить 8-битную плавающую точку с разными знаками , ICL , Добавление 32-разрядных чисел с плавающей запятой. , Как сложить и вычесть 16-разрядные числа половинной точности с плавающей запятой? , Как вычесть числа IEEE 754? ), но те, которыеПоднимите вычитание, в основном опишите его как «в основном то же самое, но вместо этого вычтите», что я не нашел чрезвычайно полезным. UAF делает говорит

Отрицательные мантиссы обрабатываются сначала преобразованием в дополнение к 2, а затем выполнением сложения. После выполнения сложения результат преобразуется обратно в форму величины знака.

Но, похоже, я не знаю, как это сделать. Я нашел this и this , который объяснял, что такое знаковая величина и как конвертировать между ней и дополнением до двух, поэтому я попытался преобразовать так:

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.34632F и 34.8532413F я получаю ответ 0x427E0716 (63.506920), когда он должен быть 0x41FC0E2D (31.506922), а с тестовыми значениями 3.34632F и -34.8532413F, Iполучить ответ 0xC27E0716 (-63.506920), когдаэто должно быть 0xC1FC0E2D (-31.506922).


Я смог исправить свою проблему, изменив способ, которым я нормализовал поплавки при вычитании.

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;

1 Ответ

0 голосов
/ 15 октября 2019

Как добавить два числа с плавающей запятой с противоположным знаком?

В основном это не так.

Для всего, что работает с числовыми типами, которые не могут полагатьсяв «обтекании двойного дополнения при переполнении» (например, с плавающей точкой, библиотеками больших чисел, ...) вы всегда получаете что-то вроде:

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);
}
...