Spostamento bit per bit e intero più grande in Bash


16

Questa è una domanda di esplorazione, il che significa che non sono completamente sicuro di cosa si tratti, ma penso che sia il più grande numero intero in Bash. Comunque, lo definirò apparentemente.

$ echo $((1<<8))
256

Sto producendo un numero intero spostando un po '. Quanto lontano posso andare?

$ echo $((1<<80000))
1

Non così lontano, a quanto pare. (1 è inaspettato e tornerò su di esso.) Ma,

$ echo $((1<<1022))
4611686018427387904

è ancora positivo. Non questo, tuttavia:

$ echo $((1<<1023))
-9223372036854775808

E un passo avanti,

$ echo $((1<<1024))
1

Perché 1 E perché il seguente?

$ echo $((1<<1025))
2
$ echo $((1<<1026))
4

Qualcuno vorrebbe analizzare questa serie?

AGGIORNARE

La mia macchina:

$ uname -a
Linux tomas-Latitude-E4200 4.4.0-47-generic #68-Ubuntu SMP Wed Oct 26 19:39:52 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

-9223372036854775808 = 0xF333333333333334. È un caso limite dall'aspetto divertente. Naturalmente, 4611686018427387904 = 0x4000000000000000. Ho il sospetto che stai colpendo una sorta di avvolgente sul numero di bit da spostare. Perché lo stai facendo, comunque?
un CVn il

6
@ MichaelKjörling Per divertimento ;-p

2
@ MichaelKjörling No, non lo è. -9223372036854775808 sarebbe 0x8000000000000000. Hai lasciato fuori la cifra finale durante il controllo: -922337203685477580 sarebbe 0xF33333333333333334.
hvd,

Risposte:


27

Bash utilizza intmax_tvariabili per l'aritmetica . Nel tuo sistema sono lunghi 64 bit, quindi:

$ echo $((1<<62))
4611686018427387904

che è

100000000000000000000000000000000000000000000000000000000000000

in binario (1 seguito da 62 0s). Spostalo di nuovo:

$ echo $((1<<63))
-9223372036854775808

che è

1000000000000000000000000000000000000000000000000000000000000000

in binario (63 0s), nell'aritmetica del complemento a due.

Per ottenere il numero intero rappresentabile più grande, devi sottrarre 1:

$ echo $(((1<<63)-1))
9223372036854775807

che è

111111111111111111111111111111111111111111111111111111111111111

in binario.

Come la punta in ilkkachu 's risposta , spostando prende l'offset modulo 64 a 64-bit x86 CPU (sia utilizzando RCLo SHL), il che spiega il comportamento che stai vedendo:

$ echo $((1<<64))
1

è equivalente a $((1<<0)). Così $((1<<1025))è $((1<<1)), $((1<<1026))è $((1<<2))...

Troverai le definizioni dei tipi e i valori massimi in stdint.h; sul tuo sistema:

/* Largest integral types.  */
#if __WORDSIZE == 64
typedef long int                intmax_t;
typedef unsigned long int       uintmax_t;
#else
__extension__
typedef long long int           intmax_t;
__extension__
typedef unsigned long long int  uintmax_t;
#endif

/* Minimum for largest signed integral type.  */
# define INTMAX_MIN             (-__INT64_C(9223372036854775807)-1)
/* Maximum for largest signed integral type.  */
# define INTMAX_MAX             (__INT64_C(9223372036854775807))

1
No, ne hai bisogno, Binary -ha una precedenza più alta di <<.
cuonglm

1
@cuonglm huh, mi serve proprio per i test su zsh ... Grazie ancora!
Stephen Kitt,

@cuonglm e Stephen. Bene, questa è una buona modifica. echo $((1<<63-1))mi dà 4611686018427387904.

@tomas yup, bash usa la precedenza dell'operatore C, zsh ha la sua impostazione predefinita dove è $((1<<63-1))uguale $(((1<<63)-1)).
Stephen Kitt,

Questo è buono a sapersi, una buona domanda e una risposta molto generosa, grazie a te sia Stephen Kitt che Tom.
Valentin B.,

4

Dal CHANGESfile per bash2.05b:

j. La shell ora esegue l'aritmetica con la dimensione intera più grande supportata dalla macchina (intmax_t), invece che lunga.

Su macchine x86_64 intmax_tcorrisponde a numeri interi a 64 bit con segno. Quindi ottieni valori significativi tra -2^63e 2^63-1. Al di fuori di questo intervallo, ottieni solo avvolgenti.


Nitpick: tra -2^63e 2^63-1, compreso.
Animale nominale,

4

Lo spostamento di 1024 ne dà uno, poiché la quantità di spostamento viene effettivamente presa dal numero di bit (64), quindi 1024 === 64 === 0, e 1025 === 65 === 1.

Lo spostamento di qualcosa di diverso da a 1chiarisce che non è un po 'di rotazione, poiché i bit più alti non si spostano verso l'estremità inferiore prima che il valore di spostamento sia (almeno) 64:

$ printf "%x\n" $(( 5 << 63 )) $(( 5 << 64 ))
8000000000000000
5

È possibile che questo comportamento dipenda dal sistema. Il codice bash a cui Stephen è collegato mostra solo un semplice spostamento, senza alcun controllo per il valore della mano destra. Se ricordo bene, i processori x86 usano solo i sei bit inferiori del valore di spostamento (in modalità 64 bit), quindi il comportamento potrebbe essere direttamente dal linguaggio macchina. Inoltre, penso che gli spostamenti di oltre la larghezza dei bit non siano chiaramente definiti in C (lo gccavverte).


2

producendo un numero intero spostando un po '. Quanto lontano posso andare?

Fino a quando la rappresentazione dei numeri interi non si avvolge (impostazione predefinita nella maggior parte delle shell).
Un numero intero a 64 bit si avvolge solitamente a 2**63 - 1.
Questo è 0x7fffffffffffffffo9223372036854775807 in dicembre.

Quel numero "+1" diventa negativo.

È lo stesso 1<<63, quindi:

$ echo "$((1<<62)) $((1<<63)) and $((1<<64))"
4611686018427387904 -9223372036854775808 and 1

Successivamente il processo si ripete di nuovo.

$((1<<80000)) $((1<<1022)) $((1<<1023)) $((1<<1024)) $((1<<1025)) $((1<<1026))

Il risultato dipende dal mod 64valore di spostamento [a] .

[a] Da: Manuale dello sviluppatore del software per architetture Intel® 64 e IA-32: Volume 2 Il conteggio è mascherato su 5 bit (o 6 bit se in modalità 64 bit e viene utilizzato REX.W). L'intervallo di conteggio è limitato da 0 a 31 (o 63 se si utilizzano la modalità 64-bit e REX.W). .

Inoltre: ricorda che lo $((1<<0))è1

$ for i in 80000 1022 1023 1024 1025 1026; do echo "$((i%64)) $((1<<i))"; done
 0 1
62 4611686018427387904
63 -9223372036854775808
 0 1
 1 2
 2 4

Quindi, tutto dipende da quanto è vicino il numero a un multiplo di 64.

Test del limite:

Il modo affidabile per verificare qual è il numero intero massimo positivo (e negativo) è testare a turno ciascuno di essi. Sono comunque meno di 64 passaggi per la maggior parte dei computer, non sarà troppo lento.

bash

Innanzitutto è necessario il numero intero più grande del modulo 2^n(set di 1 bit seguito da zeri). Possiamo farlo spostando a sinistra fino a quando il turno successivo rende il numero negativo, chiamato anche "a capo":

a=1;   while ((a>0));  do ((b=a,a<<=1))  ; done

Dov'è bil risultato: il valore prima dell'ultimo spostamento che fallisce il ciclo.

Quindi dobbiamo provare ogni bit per scoprire quali influenzano il segno di e:

c=$b;d=$b;
while ((c>>=1)); do
      ((e=d+c))
      (( e>0 )) && ((d=e))
done;
intmax=$d

Il numero intero massimo ( intmax) risulta dall'ultimo valore di d.

Sul lato negativo (minore di 0) ripetiamo tutti i test ma testiamo quando un po 'potrebbe essere fatto 0 senza avvolgerci.

Un intero test con la stampa di tutti i passaggi è questo (per bash):

#!/bin/bash
sayit(){ printf '%020d 0x%016x\n' "$1"{,}; }
a=1;       while ((a>0)) ; do((b=a,a<<=1))              ; sayit "$a"; done
c=$b;d=$b; while((c>>=1)); do((e=d+c));((e>0))&&((d=e)) ; sayit "$d"; done;
intmax=$d
a=-1;      while ((a<0)) ; do((b=a,a<<=1))              ; sayit "$b"; done;
c=$b;d=$b; while ((c<-1)); do((c>>=1,e=d+c));((e<0))&&((d=e)); sayit "$d"; done
intmin=$d       

printf '%20d max positive value 0x%016x\n' "$intmax" "$intmax"
printf '%20d min negative value 0x%016x\n' "$intmin" "$intmin"

sh

Tradotto in quasi ogni shell:

#!/bin/sh
printing=false
sayit(){ "$printing" && printf '%020d 0x%016x\n' "$1" "$1"; }
a=1;       while [ "$a" -gt 0  ];do b=$a;a=$((a<<1)); sayit "$a"; done
c=$b;d=$b; while c=$((c>>1)); [ "$c" -gt 0 ];do e=$((d+c)); [ "$e" -gt 0 ] && d=$e ; sayit "$d"; done;
intmax=$d
a=-1;      while [ "$a" -lt 0  ];do b=$a;a=$((a<<1)); sayit "$b"; done;
c=$b;d=$b; while [ "$c" -lt -1 ];do c=$((c>>1));e=$((d+c));[ "$e" -lt 0 ] && d=$e ; sayit "$d"; done
intmin=$d       

printf '%20d max positive value 0x%016x\n' "$intmax" "$intmax"
printf '%20d min negative value 0x%016x\n' "$intmin" "$intmin"

Eseguendo quanto sopra per molte shell,
tutti (tranne bash 2.04 e mksh) hanno accettato valori fino a ( 2**63 -1) in questo computer.

È interessante segnalare che l'att shell :

$ attsh --version
version         sh (AT&T Research) 93u+ 2012-08-01

ha stampato un errore sui valori di $((2^63)), non su ksh.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.