2017-10-05 3 views
0

나는 32 비트 단정도 부동 소수점 정수를 비트 연산 (if 문과 for 루프도 사용할 수 있음)을 사용하여 C로 2로 나눌 것을 요구하는 숙제가 있습니다. float의 비트는 부호없는 정수로 표시되므로 비트 연산자로 수정할 수 있습니다. 제 문제는 분열 중에 비트가 정확히 어떻게되는지 이해하는 데 어려움을 겪고 있다는 것입니다. 내 초기 계획은 부호와 가수 비트를 동일하게 유지하면서 지수 비트를 1만큼 오른쪽으로 시프트하는 것이었지만 효과가 없습니다. 예를 들어, 내 함수에 0x800000으로 표시된 비트가 주어지면 지수를 오른쪽으로 이동하면 모든 비트가 0이되기 때문에 my 함수는 0x00000000을 반환합니다. 그러나 숙제를위한 테스트 드라이버에 따르면이 시나리오에서 정답은 다음과 같습니다. 0x00400000. 이것은 지수 비트가 겉으로보기에는 가수 비트로 어떻게 또는 왜 전환 될지 확실하지 않기 때문에 실제로 혼란 스럽습니다.나누기 중에 float 비트가 어떻게됩니까?

unsigned divideFloatBy2(unsigned uf){ 
//copy the sign bit 
unsigned signBit = (1 << 31) & uf; 

//copy mantissa 
unsigned mantissa = ~0; 
mantissa >>= 9; 
mantissa &= uf; 

//copy exponent 
unsigned mask = 0xFF; 
mask <<= 23; 
unsigned exponent = (uf & mask); 
exponent >>= 23; 

exponent >>= 1; //right shift to divide by 2; 

exponent <<= 24; 

//combine all again 
unsigned ret = signBit | exponent | mantissa; 
return ret; //will be interpreted as float later 
} 

이 기능은 위에 제공된 입력과 같이 일부 입력에서는 올바르게 작동하지만 일부 입력에서는 올바르게 작동하지 않습니다. 나는이 코드를 작성하는 것보다 나눗셈 중에 float의 비트가 어떻게되는지에 대해 더 많이 묻는다.

답변

4

2의 거듭 제곱으로 정규화 된 기수 2 부동 소수점 수를 스케일링하면 지수 만 영향을 받지만 (오버플로 또는 언더 플로가 아닌 것으로 가정) 잘못된 조작을 수행하고 있다는 좋은 통찰력이 있습니다. 지수를 1 씩 오른쪽 시프 팅하는 것은 지수를 - 2로 나누는 것과 같습니다. 결과는 원래 숫자의 제곱근과 같은 크기입니다. 원래 숫자가 4가 아닌 한 전혀 그렇지 않습니다.

이진 과학 표기법으로 예제를 작성하는 것이 도움이 될 수 있습니다. 이는 기계 표현과 밀접하게 일치하기 때문입니다. 원래 번호 인 N이 1.01010x2 이라고 가정합니다. 메모를 수행

 
N/2 = N * 2-1 
     = 1.01010x2110 * 2-1 
     = 1.01010x2110-1 
     = 1.01010x2101 

는 그래서 그래, 가수 및 기호는 변경되지 않지만, 지수에 미치는 영향은 원래 프로그램과 관련하여 1


하여 감소하는 것입니다 그 사실, 당신이 설명하는 접근 방식을 올바르게 구현하지는 않습니다. 그것은 지수 비트를 23만큼 오른쪽으로 이동시켜 가장 중요한 값을 단위 자리로 가져온 다음 오른쪽으로 한 칸 더 이동하여 작업을 구현합니다. 그런 다음 비트만큼 왼쪽으로 시프트합니다. 결과 비트를 다시 올바른 위치로 가져 오려면 원래의 오른쪽 시프트를 반대로하여 23만큼 뒤로 이동해야합니다.

실제로 수행하는 연산의 효과는 편차가있는 지수가 홀수 일 때 1을 뺀 것과 같은 경우에 발생하는 최하위 지수 비트를 지우는 것입니다. 그렇기 때문에 절반의 시간에 정답을 만들어냅니다.

+0

지수의 단순 감소를 통한 IEEE-754'binary32 '값의 2 나누기가 표준화 된 부동 소수점 숫자에 대해서만 올바르게 작동한다는 것을 지적 해 주시겠습니까? 비정규 (subnormals)를 올바르게 처리하려면 추가 확장이 필요합니다. – njuffa

+0

찍은 포인트, @njuffa. 정규화 된 입력에 적용된다는 점을 명확히하기 위해 의견을 수긍했습니다. –

0

언제 ... 주어진 0x800000, 내 기능 복귀 0 ...., 정답은 ... 0x00400000입니다.

2로 최소 정상 float 값을 분할하고, 아래 # 3에 설명되어있다.


코드에 많은 문제가 있습니다.지수가> 1 때 @John Bollinger 좋은 답변 지적으로 지수를 변화보다는 감소시키는

대부분의 유한 번호는
  1. 은 올바른 것입니다.

  2. exponent == 0이 숫자가 sub-normal (또는 비정규)이고 그것의 mantissa 필드가 오른쪽으로 이동해야 할 필요가있다 (/2). 지수는 0을 유지합니다. 이동 된 비트가 1이면 2로 나눈 값이 정확하지 않습니다. 이상의 라운딩에 따라, 그리고, mantissa 조정된다 - 아마도 1

  3. 추가하여 exponent == 1이 결과는 서브 - 정상 및 정상 수치의 묵시적 비트 될 때의 mantissa 필드를 생성 및 우측 시프트되어야 (/2). 이러한 변화로 인해 위에서 언급 한 반올림이 발생할 수 있습니다. 지수가 0이됩니다. "반올림"mant0x7FFFFF의 최대 값 mant을 초과 할 수 있으며 필드를 조정해야 할 수 있습니다.

  4. exponent == MAX (255) 인 경우 숫자는 유한 한 것이 아니며 (무한 또는 Not-a-Number 임) 혼자 있어야합니다. 그것은 (압도적 공통) 2의 보수에 의존한다는 점에서 mantissa 유도와

    // unsigned signBit = (1 << 31) & uf; 
    unsigned signBit = (1u << 31) & uf; // Use an unsigned mask 
    unsigned signBit = (1LU << 31) & uf; // unsigned may be 16 bit. 
    // or better yet 
    unsigned signBit = uf & 0x80000000; 
    
  5. 코너 약점 : 1 << 31 같은

  6. 코드는 더 나은로 정의된다. 휴대용 대안 :


    unsigned

// unsigned mantissa = ~0; Incorrect mask in `mantissa` when `int` is not 2's comp. 
// unsigned mantissa = -1; correct all bits set. 
// mantissa >>= 9; 
// mantissa &= uf; 
// or simply use 
unsigned mantissa = 0x7FFFFF & uf; 
16, 32, 64 비트 등의 개선 또는 최소 폭 정확한 유형을 사용하는 것일 수있다.

#define SIGN_MASK 0x80000000 
#define EXPO_MASK 0x7F800000 
#define MANT_MASK 0x007FFFFF 

#define EXPO_SHIFT 23 
#define EXPO_MAX   (EXPO_MASK >> EXPO_SHIFT) 
#define MANT_IMPLIED_BIT (MANT_MASK + 1u) 

uint32_t divideFloatBy2(uint32_t uf){ 
    unsigned sign = uf & SIGN_MASK; 
    unsigned expo = uf & EXPO_MASK; 
    unsigned mant = uf & MANT_MASK; 

    expo >>= EXPO_SHIFT; 
    // when the number is not an infinity nor NaN 
    if (expo != EXPO_MAX) { 
    if (expo > 1) { 
     expo--; // this is the usual case 
    } else { 
     if (expo == 1) { 
     mant |= MANT_IMPLIED_BIT; 
     } 
     expo = 0; 
     unsigned round_bit = mant & 1; 
     mant /= 2; 

     if (round_bit) { 
     TBD_CODE_Handle_Rounding(round_mode, sign, &expo, &mant); 
     } 
    } 
    expo <<= EXPO_SHIFT; 
    uf = sign | expo | mant; 
    } 
    return uf; 
} 

영업 이익은 나중에 exponent ,sign 0, mantissa == 0x3, expected result is 0x2, but my returning 1. 때문에 반올림 모드가 가능성이 FE_TONEAREST 또는 가능 FE_UPWARD입니다 댓글을 달았습니다.

expo <= 1 다음과 같은 경우 다시 쓰기. 테스트 코드 - 2 조합과 4 개의 반올림 모드를 통과합니다.

some_float/2.0f이 계산 될 때 부동 소수점 환경 상태 비트에 영향을 줄 수 있습니다. 나는 처음에는 현명하게했지만 이후 관심이 있다면이 포스트에서 코드를 삭제했다.

라운딩 모드에 대한 자세한 내용은
} else { 
     if (expo == 1) { 
     expo = 0; 
     mant |= MANT_IMPLIED_BIT; 
     } 
     // Divided by 2 result inexact? 
     if (mant % 2) { 
     mant /= 2; 
     // Determine how to round 
     switch (fegetround()) { 
      case FE_DOWNWARD: 
      if (sign) mant++; 
      break; 
      case FE_TOWARDZERO: 
      break; 
      case FE_UPWARD: 
      if (!sign) mant++; 
      break; 
      default: // When mode is not known, act like FE_TONEAREST 
      // fall through 
      case FE_TONEAREST: 
      if (mant & 1) mant++; 
      break; 
     } 
     if (mant >= MANT_IMPLIED_BIT) { 
      mant = 0; 
      expo++; 
     } 
     } else { 
     mant /= 2; 
     } 
    } 

FE_... 매크로 나 here 검색하십시오.

+0

이 시나리오에서 반올림이란 정확히 무엇을 의미합니까? 즉, round_bit가 true 인 경우 TBD_CODE_Handle_Rounding()은 무엇을 수행합니까? – jburn7

+0

@ jburn7 작성해야 할 코드가 남아 있습니다. 여기에 "아마도"1을 추가함으로써 대답을 얻었습니다. 게시물에 반올림 처리 방법이 명시되어 있지 않습니다. 모든 반올림 모드를 처리하려면 (최소한 4 개) (https://www.gnu.org/software/libc/manual/html_node/Rounding.html)) 코드의 양은 꽤 많습니다. 게시물이 반올림 목표를 명확하게 지정하면 더 좋습니다. – chux

+0

오, 이제 알겠습니다. 우리는 둥근 방법에 대해서는 말하지 않았고, 0.5 * f의 비트 단위로 수행해야 할 필요가있는 것 이외에는 f가 32 비트 단정도 부동입니다. 참고로, 지수와 부호 비트가 모두 0이고 가수가 == 0x3이면 예상 결과는 0x2이지만 내 함수는 0x1을 반환하는 또 다른 예가 있습니다. 그러나, 나는 아직도 가수 비트가 십진수의 관점에서 무엇을 의미하는지 확신 할 수 없으므로,이 예제가 어떤 방법으로 반올림되는지 확신 할 수 없다. – jburn7

관련 문제