Bash의 비트 단위 작업이 예상대로 작동하지 않습니다.

Bash의 비트 단위 작업이 예상대로 작동하지 않습니다.

이상한 문제가 발생했습니다. 설명을 위해 내 컴퓨터에서 가장 큰 부호 없는 숫자( printf "%X \n" -1나에게 제공됨 FFFFFFFFFFFFFFFF)를 가져와서 일부 비트를 이동해 보겠습니다. 먼저 왼쪽으로 이동합니다.

printf "%X \n" $(( 0xFFFFFFFFFFFFFFFF<<4 ))
FFFFFFFFFFFFFFF0
printf "%X \n" $(( 0xFFFFFFFFFFFFFFFF<<8 ))
FFFFFFFFFFFFFF00
printf "%X \n" $(( 0xFFFFFFFFFFFFFFFF<<16 ))
FFFFFFFFFFFF0000

여태까지는 그런대로 잘됐다. 예상대로. 이제 오른쪽으로 이동해 보겠습니다.

printf "%X \n" $(( 0xFFFFFFFFFFFFFFFF>>4 ))
FFFFFFFFFFFFFFFF
printf "%X \n" $(( 0xFFFFFFFFFFFFFFFF>>8 ))
FFFFFFFFFFFFFFFF
printf "%X \n" $(( 0xFFFFFFFFFFFFFFFF>>16 ))
FFFFFFFFFFFFFFFF

무엇을 기다립니다? ? 왜 이것이 작동하지 않습니까? 이것은 버그입니까?


편집하다:

누군가가 제안된 부호 비트에 대한 연결을 제안할까봐 걱정됩니다. 그러나 우리는 산술에 대해 이야기하고 있는 것이 아니므로 여기서는 기호의 개념이 설 자리가 없습니다. *와 같은 다른 도구는 /산술에 사용됩니다. 비트를 조작할 수 있는 도구의 요점은 비트를 조작할 수 있다는 것입니다. 나중에 해당 비트를 서명 여부에 관계없이 표시하도록 선택하는 방법에 관계없이 말입니다. 옳은? 좋다:

printf "%u \n" -1
18446744073709551615

누구든지 어떤 아이디어가 있습니까?

편집하다:

여기의 답변은 곱셈이나 나눗셈에 대해 직접적으로 논의하므로 내 우려 사항을 더 명확하게 설명하겠습니다. 곱셈/나눗셈과 비트 이동은 서로 다른 두 가지이지만 오랫동안 프로그래머의 마음 속에서 이들 사이의 연관성을 볼 수 있었습니다. 산술을 수행할 때 비트 이동에 대해서는 부호 개념이 필요합니다. Bash는 이러한 두 가지 다른 작업을 처리할 수 있는 두 가지 도구 세트를 제공합니다. 숫자에 2를 곱하고 싶을 때 이 *도구를 사용합니다. Bash가 내부적으로 비트 이동을 사용하여 산술을 수행할 수 있다는 사실은 요점을 벗어났습니다.

답변 중 하나를 인용하자면...

부호 비트가 복사되지 않으면 결과는 부호가 없습니다. 예를 들어, 1111 00008비트 값을 오른쪽으로 한 번 이동하면0111 1000

그러나 그것은 내가 원했던 1111 0000바로 그것이 었음이 밝혀졌습니다 . 0111 1000나눗셈을 하고 싶다면 산술 연산자를 사용합니다.

어쨌든, 이동 시 어떤 비트를 채워야 하는지 명시적으로 지정하는 방법이 있습니까?

답변1

가지다오른쪽으로 이동하는 두 가지 다른 방법일반적으로 사용됩니다.

"논리적 오른쪽 시프트"는 왼쪽에 0 비트를 삽입하므로 한 위치만큼 오른쪽으로 시프트한 결과는 부호 없는 이진수를 2로 나눈 값에 해당합니다. echo $(( 16 >> 1 ))주어진 8.

또한 산술 오른쪽 시프트는 부호 비트의 복사본을 왼쪽에 삽입하므로 한 비트를 오른쪽으로 이동한 결과는 다음과 같이 나누는 것과 같습니다.징후이진수를 2로 나눕니다. , 및 를 제공 합니다 echo $(( 16 >> 1 )). 2 의 보수 를 제외하고는 실제 나눗셈의 반올림과 일치하지 않습니다 .8echo $(( -16 >> 1 ))-8-15 >> 1-8-15 / 2-7

부호 비트가 복사되지 않고 지워지면 결과는 양수입니다. 예를 들어, 1111 00008비트 값(0xf0, -16)을 오른쪽으로 한 번 이동하면 0111 1000(0x78, +120)이 됩니다.


이제 이들 중 어느 것을 사용할지는 더 까다로운 질문입니다.

실제로 많은 구현에서는 부호 있는 숫자에 산술 시프트를 사용하는 반면, 쉘 산술은 대부분 부호 있는 long에 대해 수행됩니다.

그러나 이것이 완전한 보장은 아닙니다. 쉘 연산의 POSIX 정의는 대부분의 동작에 대한 C 표준을 참조합니다. 예를 들어 연산자 테이블에는 >>어떤 유형의 이동이 수행되어야 하는지 명시되어 있지 않습니다. (바라보다:쉘 명령 언어, 2.6.4 산술 확장그리고Shell and Utilities, 1.1.2 ISO C 표준에서 파생된 개념: 산술 정밀도 및 연산)

피연산자 및 옵션 인수의 값을 포함한 정수 변수 및 상수 [...]는 ISO C 표준 부호 있는 긴 데이터 유형과 동일하게 구현됩니다 [...]

산술 연산자 및 제어 흐름 키워드의 구현은 참조된 ISO C 표준 부분 [...]의 구현과 동일해야 합니다
<<. >>섹션 6.5.7, 비트 단위 시프트 연산자

cppreference.com에서 C 연산자에 대해 이야기합니다.저것

negative 의 경우 a값은 a >> b구현에 따라 정의됩니다(대부분의 구현에서는 산술 오른쪽 시프트를 수행하므로 결과는 음수로 유지됩니다).

(이것은 아마도 모든 것이 2의 보수가 아니었던 세계의 잔재일 것입니다. 1의 보수 또는 기호 크기에 대해 오른쪽으로 이동하는 것은 2의 보수에 대해 오른쪽으로 이동하는 것과 다를 것입니다. 그러나 결과는 동일합니다. 구현이 정의됨).

다른 프로그래밍 언어,자바스크립트처럼, 다른 산술 오른쪽 시프트 >>및 논리적 오른쪽 시프트 연산자를 사용합니다 >>>. 하지만 C는 그렇지 않으며 제가 시도한 어떤 쉘도 마찬가지입니다.

또한 단어 너비보다 큰 오프셋을 사용하여 시프트를 수행하면 이상한 일이 발생하는 것도 볼 수 있습니다. x86에서는 프로세서가 이동된 값의 가장 낮은 6비트만 보기 1 << 64때문에 . 그러나 다른 프로세서에서는 결과가 다를 수 있습니다 .11 << 0(1 << 32) << 320


당신은 말한다,

그러나 여기서 상징의 개념은 설 자리가 없습니다. 내 말은, 나중에 서명되거나 서명되지 않은 상태로 표시하도록 선택하든 숫자는 숫자라는 것입니다. 그렇죠?

2의 보수 기계(예: 32x32 -> 32)에서 덧셈, 뺄셈, 곱셈의 하위 부분에 대해서도 마찬가지입니다.

그러나 이는 일반적인 곱셈이나 나눗셈의 고차 부분에는 해당되지 않습니다. 8비트 값은 0xff부호 없는 숫자 255 또는 부호 있는 숫자 -1을 나타낼 수 있습니다. 예를 들어, 8x8 -> 16 곱셈은 부호 있는 값(-1 * -1)인지 부호 없는 값(255 * 255)인지에 따라 OR 0xff * 0xff입니다 . 또한 예를 들어 부호가 있는지(-1/3 == 0) 또는 부호가 없는지(255/3 == 85)에 따라 is 또는 입니다.0x00010xfe010xff / 300x55

답변2

  • 논리적 오른쪽 시프트는 0으로 채워집니다.

    [01010110] >> 2 becomes [00010101]
    [11010110] >> 2 becomes [00110101]
    
  • 산술 오른쪽 시프트는 가장 중요한 비트를 채웁니다.

    [01010110] >> 2 becomes [00010101]
    [11010110] >> 2 becomes [11110101]
    

논리적 시프트를 예상했지만 Bash는 산술 시프트를 수행합니다. 그것은 실제로 서명에 관한 것입니다.

이것수동설명하다

평가는 고정 너비 정수로 이루어집니다. 연산자와 그 우선순위, 결합성, 값은 C언어와 동일합니다.

정수 상수는 C 언어 정의를 따르며 접미사나 문자 상수가 없습니다. 0으로 시작하는 상수는 8진수로 해석됩니다. 앞에 "0x" 또는 "0X"가 있으면 16진수를 나타냅니다.

그리고 인용한 것컴퓨터 시스템, Randall Bryant 및 David O'Haralan:

C 표준은 어떤 유형의 오른쪽 시프트를 사용해야 하는지 정확하게 정의하지 않습니다. 부호 없는 데이터의 경우 오른쪽 시프트가 논리적이어야 합니다. 부호 있는 데이터(기본값)의 경우 산술 또는 논리 시프트를 사용할 수 있습니다. (...) 그러나 실제로는 거의 모든 컴파일러/머신 조합이 서명된 데이터에 대해 산술 오른쪽 시프트를 사용하며 많은 프로그래머가 이것이 사실이라고 가정합니다.

누군가 Stack Overflow에서 다음과 같은 질문을 했습니다.C의 시프트 연산자(<<, >>)는 산술인가요, 아니면 논리인가요?

답변3

Bash는 다른 쉘처럼 동작합니다. 쉘은 상당히 높은 수준의 언어이지만 일반적인 정수 연산 대신 제한된 크기의 연산을 제공하는 것은 설계 오류일 수 있지만 현재로서는 변경되지 않습니다.

나중에 부호가 있거나 없는 것으로 표시하도록 선택했는지 여부에 관계없이 숫자는 숫자입니다. 그렇죠?

예, 숫자는 숫자입니다. 하지만 쉘은 그렇지 않습니다.실수 정수. 그들만기계 정수, 그들은 더 제한적이고 이상한 방식으로 행동합니다.

곱셈/나눗셈과 비트 이동은 서로 다른 두 가지이지만 오랫동안 프로그래머의 마음 속에서 이들 사이의 연관성을 볼 수 있었습니다. 산술을 수행할 때 비트 이동에 대해서는 부호 개념이 필요합니다.

사실 서로 다르지만 연관되어 있다는 사실을 오해하고 있는 것입니다. 비트 시프트에는 확실히 부호라는 개념이 있습니다!

기계 정수는 여러 가지 다른 방식으로 해석될 수 있습니다.

  • N 값 비트의 배열로. 이것이 기억 속의 표현이다.
  • 비트 배열의 경우 첫 번째 비트는 부호 비트이고 다른 N-1은 값 비트입니다. 부호 비트는 N 값 비트 해석의 최상위 비트입니다.
  • 0과 2^N-1 사이의 정수("부호 없는 정수")로서 해당 값은 N 값 비트로 표시됩니다.
  • -2^(N-1)과 2^(N-1)-1("부호 있는 정수") 사이의 정수이며, 해당 값은 N-1 값 비트와 부호 비트로 표시됩니다. 부호 비트가 0이면 값은 값 비트로 제공됩니다. 부호 비트가 1이면 값은 음수이고 이론적으로는 값 비트를 기준으로 값을 계산하는 방법이 있지만 실제로는 Unix 쉘을 실행하고 나누기를 수행하는 플랫폼을 모릅니다.2의 보수. 2의 보수 표현을 사용하는 경우 부호 비트는 1이고 값 비트는 정수 값을 나타냅니다.엑스예 - (2^(N-1) -엑스).
  • 정수 모듈로 2^N("정수 모듈로"). 값은 2^N 모듈로 부호 없는 값입니다. 2의 보수 표현에서 이는 모듈로 2^N의 부호 있는 값이기도 합니다(이것이 2의 보수의 주요 장점입니다).

관련된 모든 숫자가 0과 2^N-1 사이에 있는 한 어떤 해석이든 선택할 수 있으며 모든 작업은 직관적인 결과를 제공합니다. 그러나 피연산자 또는 결과가 이 범위를 벗어나는 경우(음수 또는 너무 큰 경우) 어떤 해석을 사용하는지가 중요합니다.

예제를 읽기 쉽게 유지하기 위해 N=4 (2^N = 16) 및 2의 보수를 사용하는 예제를 제공하겠습니다. 실제로 최신 버전의 bash에서는 N=64입니다. 이전 버전의 bash 및 기타 셸에서 N은 32비트 플랫폼에서 32일 수 있습니다.

  • 수치 분석은 모듈로 2^N으로 수행됩니다. 예를 들어, N=4인 경우 sum 319sum은 모두 -13비트로 표현되는 동일한 숫자를 나타내고 0011, -3합계는 13비트로 표현됩니다 1101.
  • 8진수와 16진수 인쇄에서는 숫자를 부호 없는 정수로 처리합니다. 예를 들어, 비트 표현이 있는 숫자는 110116진수 형식으로 인쇄됩니다 d. 첫 번째 비트는 부호 비트가 아닌 값 비트로 해석됩니다.
  • 10진수 인쇄에서는 숫자를 부호 있는 정수로 처리합니다. 예를 들어, 비트 표현이 있는 숫자는 110116진수 형식으로 인쇄됩니다 -3. 첫 번째 비트는 값 비트가 아닌 부호 비트로 해석됩니다.
  • 덧셈, 뺄셈, 곱셈은 모듈로 2^N으로 수행됩니다. 이는 범위 제한 없이 부호 없는 정수 또는 부호 있는 정수에 대해 연산을 수행한 다음 원하는 범위 내에서 나머지 모듈로 2^N을 취하는 것과 같습니다.
  • 나눗셈은 부호 있는 정수에 대해 수행됩니다. 덧셈, 뺄셈, 곱셈과 달리 이 표현의 선택은 중요합니다. 부호 없는 정수나 숫자 모듈로를 사용하면 다른 결과가 나옵니다. (비틀림이 있습니다: (-2^(N-1) / -1)은 결과가 범위를 벗어난 유일한 경우입니다. 내가 아는 한 모든 쉘은 값을 -2^(N-1)로 지정합니다. ).)
  • 비트 연산은 비트 표현에 대해 작동합니다. 대부분의 작업에서 부호 있는 표현을 선택하든 부호 없는 표현을 선택하든 상관없습니다. 모든 비트에 대해 동일한 작업이 수행됩니다. 그러나 교대근무의 경우에는 중요하며 다음과 같습니다.
    • 왼쪽 시프트는 첫 번째 비트를 값 비트로 처리합니다. 예를 들어, 비트 표현이 왼쪽으로 1만큼 이동한 숫자는 0111비트 표현을 갖습니다 1110. 이렇게 하면 왼쪽 이동이 이루어집니다.케이2^를 곱하는 것과 같습니다.케이.
    • 내가 본 모든 쉘에서 오른쪽 이동은 첫 번째 비트를 값 비트로 전파되는 부호 비트로 처리합니다. 예를 들어, 비트 표현이 오른쪽으로 1만큼 이동된 숫자는 1101비트 표현을 갖습니다 1110. 이렇게 하면 오른쪽 이동이 이루어집니다.케이2^로 나누는 것과 같습니다.케이. 이것은 ... 불리운다산술 시프트, .
  • 비교는 부호 있는 정수에 대해 작동합니다. 예를 들어, 비트로 표현된 숫자는 음수로 간주되므로 비트로 1111표현된 숫자보다 작습니다 .00001111

따라서 64비트 예로 돌아가서 0xFFFFFFFFFFFFFFFF>>4yes 입니다 0xFFFFFFFFFFFFFFFF. 왜냐하면 >>왼쪽 피연산자는 부호 있는 비트 배열로 해석되고 부호 설정 비트는 상위 4개의 공개 값 비트 위치로 전파되기 때문입니다. 와 은 동일한 기계 정수를 나타내는 다른 방법이기 -1 >> 4때문에 와 정확히 동일하게 작동합니다 .-10xFFFFFFFFFFFFFFFF

답변4

구경하다존재하다.

첫 번째 1(왼쪽부터)은 기호를 나타내므로 복사하면 됩니다. 시도하는 경우:

printf "%X \n" $(( 0x7FFFFFFFFFFFFFFF>>4 ))

당신은 당신이 기대하는 것을 얻을 수 있습니다.

관련 정보