Domanda

Perché Java non include il supporto per numeri interi senza segno?

Mi sembra una strana omissione, dato che consentono di scrivere codice che ha meno probabilità di produrre overflow su input inaspettatamente grandi.

Inoltre, l'uso di numeri interi senza segno può essere una forma di autocertificazione, poiché indicano che il valore che l'int non firmato doveva contenere non dovrebbe mai essere negativo.

Infine, in alcuni casi, numeri interi senza segno possono essere più efficienti per determinate operazioni, come la divisione.

Qual è lo svantaggio di includerli?

È stato utile?

Soluzione

Questo è tratto da una intervista con Gosling e altri , sulla semplicità:

  

Gosling: Per me come progettista di lingue, che non mi considero proprio come in questi giorni, cosa è "semplice" il significato alla fine è stato che potevo aspettarmi che J. Random Developer avesse le specifiche in testa. Tale definizione afferma che, ad esempio, Java non lo è - e in effetti molti di questi linguaggi finiscono con molti casi angolari, cose che nessuno capisce davvero. Interroga qualsiasi sviluppatore C su unsigned, e molto presto scopri che quasi nessuno sviluppatore C in realtà capisce cosa succede con unsigned, cos'è l'aritmetica senza segno. Cose del genere hanno reso C complesso. La parte linguistica di Java è, credo, piuttosto semplice. Le librerie che devi cercare.

Altri suggerimenti

Leggendo tra le righe, penso che la logica fosse qualcosa del genere:

  • in generale, i progettisti Java volevano semplificare il repertorio dei tipi di dati disponibili
  • per scopi quotidiani, hanno ritenuto che l'esigenza più comune fosse quella di tipi di dati firmati
  • per implementare determinati algoritmi, a volte è necessaria un'aritmetica senza segno, ma il tipo di programmatori che implementerebbe tali algoritmi avrebbe anche le conoscenze per "lavorare attorno". fare aritmetica senza segno con tipi di dati firmati

Principalmente, direi che è stata una decisione ragionevole. Forse avrei:

  • reso byte senza segno, o almeno hanno fornito alternative firmate / non firmate, possibilmente con nomi diversi, per questo tipo di dati (renderlo firmato è buono per coerenza, ma quando hai mai bisogno di un byte con segno?)
  • eliminato con 'short' (quando hai usato l'ultima volta l'aritmetica firmata a 16 bit?)

Tuttavia, con un po 'di kludging, le operazioni su valori senza segno fino a 32 bit non sono troppo male e la maggior parte delle persone non ha bisogno di divisione o confronto a 64 bit senza segno.

Questa è una domanda più vecchia e pat ha menzionato brevemente char, ho pensato che avrei dovuto approfondire questo aspetto per gli altri che lo guarderanno lungo la strada. Diamo un'occhiata più da vicino ai tipi primitivi Java:

byte - intero con segno a 8 bit

short - intero con segno a 16 bit

int - intero con segno a 32 bit

long - intero con segno a 64 bit

char - carattere a 16 bit (intero senza segno)

Sebbene char non supporti l'aritmetica unsigned , in sostanza può essere trattata come un intero unsigned . Dovresti ricollegare esplicitamente le operazioni aritmetiche in char , ma ti fornisce un modo per specificare i numeri non firmati .

char a = 0;
char b = 6;
a += 1;
a = (char) (a * b);
a = (char) (a + b);
a = (char) (a - 16);
b = (char) (b % 3);
b = (char) (b / a);
//a = -1; // Generates complier error, must be cast to char
System.out.println(a); // Prints ? 
System.out.println((int) a); // Prints 65532
System.out.println((short) a); // Prints -4
short c = -4;
System.out.println((int) c); // Prints -4, notice the difference with char
a *= 2;
a -= 6;
a /= 3;
a %= 7;
a++;
a--;

Sì, non esiste un supporto diretto per numeri interi senza segno (ovviamente, non dovrei riportare la maggior parte delle mie operazioni in caratteri se ci fosse un supporto diretto). Tuttavia, esiste sicuramente un tipo di dati primitivo senza segno. Mi piacerebbe vedere anche un byte senza segno, ma immagino che raddoppiare il costo della memoria e invece usare char sia un'opzione praticabile.


Modifica

Con JDK8 ci sono nuove API per Long e Numero intero che forniscono metodi di supporto quando si trattano i valori long e int come valori non firmati.

  • compareUnsigned
  • divideUnsigned
  • parseUnsignedInt
  • parseUnsignedLong
  • remainderUnsigned
  • toUnsignedLong
  • toUnsignedString

Inoltre, Guava fornisce una serie di metodi di supporto per fare cose simili per i tipi interi che aiuta a colmare il divario lasciato dalla mancanza di supporto nativo per gli interi non firmati .

Java ha tipi senza segno, o almeno uno: char è un corto senza segno. Quindi, qualunque scusa Gosling sollevi, in realtà è solo la sua ignoranza perché non ci sono altri tipi senza segno.

Anche tipi corti: i pantaloncini sono usati continuamente per i contenuti multimediali. Il motivo è che è possibile inserire 2 campioni in un singolo unsigned long a 32 bit e vettorializzare molte operazioni. Stessa cosa con dati a 8 bit e byte senza segno. È possibile inserire 4 o 8 campioni in un registro per la vettorializzazione.

Non appena gli integri firmati e non firmati si mescolano in un'espressione, le cose iniziano a diventare confuse e probabilmente perderanno informazioni. Limitare Java a ints firmati chiarisce davvero le cose. Sono contento di non dovermi preoccupare dell'intera attività firmata / non firmata, anche se a volte mi manca l'ottavo bit in un byte.

http://skeletoncoder.blogspot.com/ 2006/09 / java-tutorial-perché-non-unsigned.html

Questo ragazzo dice perché lo standard C definisce le operazioni che coinvolgono ints non firmati e firmati da trattare come non firmati. Ciò potrebbe far sì che numeri interi con segno negativo vengano spostati in un int grande senza segno, causando potenzialmente bug.

Penso che Java vada bene così com'è, l'aggiunta di unsigned lo complicherebbe senza molto guadagno. Anche con il modello intero semplificato, la maggior parte dei programmatori Java non sa come si comportano i tipi numerici di base: basta leggere il libro Java Puzzlers per vedere quali idee sbagliate potresti avere.

Per quanto riguarda i consigli pratici:

  • Se i tuoi valori hanno dimensioni alquanto arbitrarie e non rientrano in int , usa long . Se non rientrano in long usa BigInteger.

  • Utilizzare i tipi più piccoli solo per gli array quando è necessario risparmiare spazio.

  • Se hai bisogno esattamente di 64/32/16/8 bit, usa long / int / short / byte e smetti di preoccuparti del bit del segno, ad eccezione di divisione, confronto, spostamento a destra e casting.

Vedi anche questa risposta su " porting di un generatore di numeri casuali da C a Java " ;.

Con JDK8 ha un certo supporto per loro.

Potremmo ancora vedere il pieno supporto di tipi non firmati in Java nonostante le preoccupazioni di Gosling.

So che questo post è troppo vecchio; tuttavia per il tuo interesse, in Java 8 e versioni successive, puoi utilizzare il tipo di dati int per rappresentare un numero intero a 32 bit senza segno, che ha un valore minimo di 0 e un valore massimo di 2 32 & # 8722; 1. Utilizzare la classe Integer per utilizzare il tipo di dati int come numero intero senza segno e metodi statici come compareUnsigned () , divideUnsigned () ecc. sono stati aggiunti alla classe Integer per supportare le operazioni aritmetiche per numeri interi senza segno.

Ho sentito storie che dovevano essere incluse vicino alla versione originale di Java. Oak è stato il precursore di Java, e in alcuni documenti di specifiche si parlava di valori usati. Sfortunatamente, questi non sono mai arrivati ??al linguaggio Java. Per quanto qualcuno è stato in grado di capire che non sono stati implementati, probabilmente a causa di un limite di tempo.

Una volta ho seguito un corso C ++ con qualcuno nel comitato per gli standard C ++ che ha suggerito che Java ha preso la decisione giusta per evitare di avere numeri interi senza segno perché (1) la maggior parte dei programmi che usano numeri interi senza segno possono fare altrettanto con numeri interi firmati e questo è più naturale in termini di come la gente pensa, e (2) l'utilizzo di numeri interi senza segno si traduce in molti problemi facili da creare ma difficili da debug come il trabocco aritmetico dei numeri interi e la perdita di bit significativi durante la conversione tra tipi con segno e senza segno. Se si sottrae erroneamente 1 da 0 utilizzando numeri interi con segno, spesso si verifica un arresto più rapido del programma e si rende più facile trovare il bug che se si avvolge a 2 ^ 32 - 1, e compilatori e strumenti di analisi statica e controlli di runtime devono supponi di sapere cosa stai facendo da quando hai scelto di usare l'aritmetica senza segno. Inoltre, numeri negativi come -1 possono spesso rappresentare qualcosa di utile, come un campo che viene ignorato / predefinito / non impostato mentre se si utilizzava unsigned si dovrebbe riservare un valore speciale come 2 ^ 32 - 1 o qualcosa di simile.

Molto tempo fa, quando la memoria era limitata e i processori non funzionavano automaticamente su 64 bit contemporaneamente, ogni bit contava molto di più, quindi avere byte o short con segno o senza segno contava molto più spesso ed era ovviamente la giusta decisione di progettazione . Oggi solo usare un int firmato è più che sufficiente in quasi tutti i normali casi di programmazione e se il tuo programma ha davvero bisogno di usare valori maggiori di 2 ^ 31 - 1, spesso vuoi solo un lungo comunque. Una volta che sei nel territorio dell'utilizzo dei long, è ancora più difficile trovare un motivo per cui non riesci davvero a cavartela con 2 ^ 63 - 1 numeri interi positivi. Ogni volta che andremo ai processori a 128 bit sarà ancora meno un problema.

La tua domanda è " Perché Java non supporta inserti non firmati " ;?

E la mia risposta alla tua domanda è che Java vuole che tutti i suoi tipi primitivi: byte , carattere , breve , int e long devono essere trattati come byte , word , dword e qword , esattamente come nell'assembly, e gli operatori Java sono operazioni firmate su tutti i suoi tipi primitivi ad eccezione di carattere , ma solo su carattere sono senza segno solo a 16 bit.

Quindi i metodi statici suppongono di essere le operazioni senza segno anche sia a 32 che a 64 bit.

È necessaria la classe finale, i cui metodi statici possono essere chiamati per le operazioni non firmate .

Puoi creare questa classe finale, chiamarla come vuoi e implementare i suoi metodi statici.

Se non hai idea di come implementare i metodi statici, questo link può aiutarti.

Secondo me, Java non è simile al C ++ affatto , se supporta tipi non firmati sovraccarico dell'operatore, quindi penso che Java dovrebbe essere trattato come un linguaggio completamente diverso sia dal C ++ che dal C.

A proposito, è anche completamente diverso nel nome delle lingue.

Quindi non consiglio a Java di digitare un codice simile a C e non consiglio di digitare un codice simile a C ++, perché in Java non sarai in grado di fare quello che vuoi fare dopo in C ++, cioè il codice non continuerà ad essere C ++ come per niente e per me è un male codificare in questo modo, per cambiare lo stile nel mezzo.

Consiglio di scrivere e usare metodi statici anche per le operazioni firmate, quindi non vedete nella combinazione di codice di operatori e metodi statici sia per le operazioni firmate che per quelle non firmate, a meno che non siano necessarie solo operazioni firmate nel codice e va bene usare solo gli operatori.

Inoltre consiglio di evitare l'uso di brevi , int e lunghi tipi primitivi e di usare word , < strong> dword e qword rispettivamente, e stai per chiamare i metodi statici per operazioni senza segno e / o operazioni con segno invece di usare operatori.

Se stai per eseguire solo operazioni firmate e utilizzare gli operatori solo nel codice, allora va bene usare questi tipi primitivi short , int e lunga .

In realtà parola , dword e qword esistono n't nella lingua, ma puoi creare nuova classe per ciascuno e l'implementazione di ciascuno dovrebbe essere molto semplice:

La classe parola contiene solo il tipo primitivo breve , la classe dword contiene solo il tipo primitivo int e la classe qword contiene solo il tipo primitivo long . Ora tutti i metodi senza segno e firmati come statici o meno come la tua scelta, puoi implementare in ogni classe, cioè tutte le operazioni a 16 bit sia senza segno che firmate dando nomi di significato sulla classe parola , tutto le operazioni a 32 bit sia senza segno che firmate dando nomi di significato sulla classe dword e tutte le operazioni a 64 bit sia senza segno che firmate dando nomi di significato sulla classe qword .

Se non ti piace dare troppi nomi diversi per ogni metodo, puoi sempre usare il sovraccarico in Java, bene leggere che Java non lo ha rimosso anche!

Se vuoi metodi piuttosto che operatori per 8 bi

Perché il tipo unsigned è puro male.

Il fatto che in C unsigned - int produca unsigned è ancora più malvagio.

Ecco un'istantanea del problema che mi ha bruciato più di una volta:

// We have odd positive number of rays, 
// consecutive ones at angle delta from each other.
assert( rays.size() > 0 && rays.size() % 2 == 1 );

// Get a set of ray at delta angle between them.
for( size_t n = 0; n < rays.size(); ++n )
{
    // Compute the angle between nth ray and the middle one.
    // The index of the middle one is (rays.size() - 1) / 2,
    // the rays are evenly spaced at angle delta, therefore
    // the magnitude of the angle between nth ray and the 
    // middle one is: 
    double angle = delta * fabs( n - (rays.size() - 1) / 2 ); 

    // Do something else ...
}

Hai già notato il bug? Confesso di averlo visto solo dopo essere intervenuto con il debugger.

Poiché n è di tipo non firmato size_t l'intera espressione n - (rays.size () - 1) / 2 viene valutata come < code> non firmato . Tale espressione è intesa come posizione firmata del raggio n da quello medio: il 1o raggio da quello medio sul lato sinistro avrebbe la posizione -1, il primo a destra avrebbe la posizione +1, ecc. Dopo aver preso il valore abs e moltiplicato per l'angolo delta otterrei l'angolo tra n e il raggio uno.

Sfortunatamente per me l'espressione sopra conteneva il male non firmato e invece di valutare, diciamo -1, ha valutato 2 ^ 32-1. La successiva conversione in double ha sigillato il bug.

Dopo uno o due bug causati dall'uso improprio dell'aritmetica unsigned , bisogna cominciare a chiedersi se il bit extra che si ottiene valga la pena. Sto cercando, per quanto possibile, di evitare qualsiasi uso di tipi di unsigned in aritmetica, anche se lo uso ancora per operazioni non aritmetiche come le maschere binarie.

Ci sono alcune gemme nella specifica 'C' che Java ha lasciato cadere per ragioni pragmatiche ma che lentamente stanno tornando indietro con la domanda degli sviluppatori (chiusure, ecc.)

Ne cito un primo perché è legato a questa discussione; l'adesione dei valori del puntatore all'aritmetica di numeri interi senza segno. E, in relazione a questo argomento di discussione, la difficoltà di mantenere la semantica Unsigned nel mondo firmato di Java.

Immagino che se si dovesse ottenere un alter ego di Dennis Ritchie per avvisare il team di progettazione di Gosling che avrebbe suggerito di dare a Signed uno "zero a infinito", in modo che tutte le richieste di offset degli indirizzi aggiungessero prima la loro DIMENSIONE ALGEBRAICA per ovviare valori negativi.

In questo modo, qualsiasi offset generato dall'array non può mai generare un SEGFAULT. Ad esempio in una classe incapsulata che chiamo RingArray di doppi che ha bisogno di un comportamento senza segno - in "loop auto-rotante" contesto:

// ...
// Housekeeping state variable
long entrycount;     // A sequence number
int cycle;           // Number of loops cycled
int size;            // Active size of the array because size<modulus during cycle 0
int modulus;         // Maximal size of the array

// Ring state variables
private int head;   // The 'head' of the Ring
private int tail;   // The ring iterator 'cursor'
// tail may get the current cursor position
// and head gets the old tail value
// there are other semantic variations possible

// The Array state variable
double [] darray;    // The array of doubles

// somewhere in constructor
public RingArray(int modulus) {
    super();
    this.modulus = modulus;
    tail =  head =  cycle = 0;
    darray = new double[modulus];
// ...
}
// ...
double getElementAt(int offset){
    return darray[(tail+modulus+offset%modulus)%modulus];
}
//  remember, the above is treating steady-state where size==modulus
// ...

Il precedente RingArray non avrebbe mai "ottenuto" da un indice negativo, anche se un richiedente malintenzionato avesse tentato di farlo. Ricorda, ci sono anche molte richieste legittime per chiedere valori di indice (negativi) precedenti.

NB: il modulo% esterno de-fa riferimento a richieste legittime mentre il modulo% interno maschera la palese negligenza dai negativi più negativi del modulo. Se mai dovesse apparire in un Java + .. + 9 || 8 + .. + spec, quindi il problema diventerebbe davvero un "programmatore che non può" auto-ruotare " GUASTO'.

Sono sicuro che la cosiddetta "deficienza" di Java senza segno può essere compensata con il sopra descritto.

PS: solo per dare un contesto al servizio di pulizia RingArray sopra, ecco un'operazione candidata 'set' per abbinare l'operazione sopra l'elemento 'get':

void addElement(long entrycount,double value){ // to be called only by the keeper of entrycount
    this.entrycount= entrycount;
    cycle = (int)entrycount/modulus;
    if(cycle==0){                       // start-up is when the ring is being populated the first time around
        size = (int)entrycount;         // during start-up, size is less than modulus so use modulo size arithmetic
        tail = (int)entrycount%size;    //  during start-up
    }
    else {
        size = modulus;
        head = tail;
        tail = (int)entrycount%modulus; //  after start-up
    }
    darray[head] = value;               //  always overwrite old tail
}

Mi viene in mente uno sfortunato effetto collaterale. Nei database Java integrati, il numero di ID che è possibile avere con un campo ID a 32 bit è 2 ^ 31, non 2 ^ 32 (~ 2 miliardi, non ~ 4 miliardi).

Il motivo per cui l'IMHO è perché sono / erano troppo pigri per implementare / correggere quell'errore. Suggerire che i programmatori C / C ++ non capisce unsigned, struttura, unione, bit flag ... è semplicemente assurdo.

Ether stavi parlando con un programmatore di base / bash / java sul punto di iniziare a programmare alla C, senza alcuna reale conoscenza di questo linguaggio o stai semplicemente parlando fuori di testa. ;)

quando ti occupi quotidianamente del formato da file o hardware, inizi a mettere in discussione cosa diavolo stavano pensando.

Un buon esempio in questo caso potrebbe essere il tentativo di utilizzare un byte senza segno come loop auto-rotante. Per quelli di voi che non comprendono l'ultima frase, come mai ti definisci programmatore.

DC

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top