Perché Java non supporta gli integ non firmati?


374

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 è il lato negativo di includere questi?


137
Non lo so, ma mi dà fastidio; ad esempio è molto più difficile scrivere il codice di rete in questo modo.
Tamas Czinege,

20
Vorrei che ci fossero solo due tipi nella lingua / database / ... mondo: numero e stringa :)
Liao

5
Scrivere codice di rete non è affatto più difficile. BTW InputStream.read (), restituisce un byte senza segno, non uno con segno ad esempio, quindi l'esempio di rete è un IMHO di confusione. L'unica cosa che ti confonde è che pensi che scrivere un valore con segno sia diverso dalla scrittura di un valore senza segno. cioè se in realtà non sai cosa sta succedendo a livello di byte.
Peter Lawrey,

19
@ZachSaw - Ho anche fatto una doppia ripresa quando ho visto un designer linguistico fare quella citazione. Non c'è niente di più semplice di un numero intero senza segno. Gli interi firmati sono complicati. Soprattutto se si considera che il bit brilla a livello di transistor. E come si sposta un numero intero con segno? Ho dovuto concludere che il progettista di Java ha un serio problema nel comprendere la logica booleana.
PP.

8
Per me diventa più difficile eseguire qualsiasi elaborazione delle immagini con le immagini che bytenon sono in grado di fornire un 140livello di grigio semplice ma -116che è necessario & 0xffottenere il valore corretto.
Matthieu,

Risposte:


193

Questo è da un'intervista a Gosling e altri , sulla semplicità:

Gosling: Per me come progettista del linguaggio, che in realtà non mi considero come al giorno d'oggi, quale significato "semplice" alla fine è stato possibile aspettarmi che J. Random Developer avesse in mente le specifiche. 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, che 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.


222
Non dovrò essere d'accordo con Gosling qui con un esempio specifico (da CLR non meno). Cosa c'è di più confuso nel dare a una matrice un valore di lunghezza intera con segno o una lunghezza senza segno? È impossibile per un array avere una lunghezza negativa ma la nostra API indica che è possibile.
JaredPar,

18
L'argomento di rendere Java semplice è parte di ciò che ci ha portato nel caos con una mancanza di modelli che alla fine hanno portato nel linguaggio perché le alternative erano così ingombranti. Penso che uno potrebbe supportare ints senza segno con una classe appropriata, tuttavia non ha bisogno di prims
Uri,

59
Se Java ha bisogno di numeri interi senza segno perché gli indici di matrice non possono essere negativi, allora ha bisogno anche di subrange (alla Pascal) perché un indice di matrice non può essere maggiore della dimensione della matrice.
Wayne Conrad,

81
Ok, ha appena detto i vantaggi di non avere tipi senza segno. Ora contiamo gli svantaggi ...
Moshe Revah,

83
Preferisco la semplicità del codice rispetto alla semplicità del linguaggio. Ecco perché odio Java.
Pijusn,

50

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, ritenevano che l'esigenza più comune fosse quella di tipi di dati firmati
  • per l'implementazione di determinati algoritmi, talvolta è necessaria l'aritmetica senza segno, ma il tipo di programmatori che implementerebbe tali algoritmi avrebbe anche le conoscenze per "aggirare" facendo l'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 mai hai 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.


2
Mi piacerebbe avere anche byte senza segno, ma sospetto che il vantaggio della completa coerenza tra i tipi di numero intero superi la convenienza che porterebbero i byte senza segno.
Alan Moore,

64
"Per scopi quotidiani, hanno ritenuto che l'esigenza più comune fosse per i tipi di dati firmati". Nel mio codice C ++, mi trovo più spesso a pensare "Perché mai sto usando un intero con segno qui invece di un senza segno ?!". Ho la sensazione che "firmato" sia l'eccezione piuttosto che la regola (ovviamente, dipende dal dominio, ma c'è una ragione per cui numeri interi positivi sono chiamati numeri naturali ;-)).
Luc Touraille,

15
pollice su per la chiamata di byte senza segno, quando si esegue l'elaborazione delle immagini, assumendo che i byte siano senza segno (come dovrebbe essere), mi ha fatto passare ore a eseguire il debug.
Helin Wang,

7
ti saresti sorpreso della frequenza con cui gli shortalgoritmi defltate / gzip / infllate sono a 16 bit e fanno molto affidamento sui cortometraggi ... o almeno short[][è vero che sono nativi - eppure gli impianti java dell'algoritmo portano terrabyte di dati]. Quest'ultimo ( short[]) ha un vantaggio significativo in int[]quanto richiede una quantità di memoria doppia e una memoria inferiore = migliori proprietà di memorizzazione nella cache, prestazioni molto migliori.
bestsss

8
Sebbene in una particolare applicazione, dovresti misurare se l'uso dei pantaloncini ti dà prestazioni migliori piuttosto che supporre che sia vero. È possibile che il jiggery-pokery extra richiesto per manipolare i pantaloncini piuttosto che gli ints (che di solito è il tipo che il processore "ama usare") possa effettivamente pregiudicare le prestazioni in una particolare applicazione. Non sempre, ma dovresti testare, non assumere.
Neil Coffey,

19

Questa è una domanda più vecchia e Pat ha menzionato brevemente il personaggio, 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 - Numero intero con segno a 8 bit

short - Numero intero con segno a 16 bit

int - Numero intero con segno a 32 bit

long - Numero intero con segno a 64 bit

char - Carattere a 16 bit (intero senza segno)

Sebbene charnon supporti l' unsignedaritmetica, in sostanza può essere trattato come un unsignednumero intero. Dovresti ricollegare esplicitamente le operazioni aritmetiche char, ma ti fornisce un modo per specificare i unsignednumeri.

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 sarebbe piaciuto vedere anche un byte senza segno, ma immagino che raddoppiare il costo della memoria e invece usare char sia un'opzione praticabile.


modificare

Con JDK8 ci sono nuove API per Longe Integerche forniscono metodi di supporto durante il trattamento longe i intvalori come valori senza segno.

  • 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 unsignedinteri.


2
Tuttavia, charè troppo piccolo per supportare l' longaritmetica, per esempio.

3
Questo potrebbe essere uno svantaggio di Java

Sperando che supportino valori non firmati per byte. Rende le cose più facili.
miscela del

15

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 i tipi corti: i pantaloncini vengono 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.


37
Sì, sono sicuro che Gosling è molto ignorante su Java rispetto a te.
jakeboxer,

Java consente l'esecuzione dell'aritmetica direttamente su quantità di byte senza segno o i valori vengono sempre promossi? Avere un tipo senza segno per l'archiviazione, ma eseguire sempre l'aritmetica su un tipo con segno che è abbastanza grande da adattarlo funziona semanticamente bene, ma renderebbe più costose le operazioni su tipi senza segno della stessa dimensione degli interi "normali".
supercat

2
È un cattivo stile da usare charper tutto tranne che per i personaggi.
Starblue,

5
@starblue Certo che lo è, ma è un trucco aggirare una limitazione della lingua
Basic

14

Non appena firmati e unsigned int sono mescolati in un'espressione cose iniziano a farsi disordinato e probabilmente sarà perdere le informazioni. Limitare Java agli 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.


12
Per quanto riguarda il missaggio firmato / non firmato: potresti avere tipi non firmati, ma non consentire il missaggio (o richiedere cast espliciti). Tuttavia, non è chiaro se sia necessario.
sleske,

2
In C ++ devi cospargere static_castmolto s per mescolarli. È davvero disordinato.
Raedwald,

4
L'ottavo bit è lì, cerca solo di nascondersi come segno.
Starblue,

Le cose si sporcano solo con tipi di 32 bit o più grandi. Non vedo alcun motivo per cui Java non avrebbe dovuto byteessere firmato come in Pascal.
supercat

12
Vieni a trovarmi quando riscontri problemi con l'elaborazione delle immagini in Java, dove ti aspetti che i byte non siano firmati. Allora saprai che & 0xFFogni promozione da byte a int rende il codice ancora più disordinato.
bit2shift

12

http://skeletoncoder.blogspot.com/2006/09/java-tutorials-why-no-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 si spostino all'interno di un int non firmato di grandi dimensioni, causando potenzialmente bug.


34
Anche gli interi con segno Java rotolano. Non vedo il tuo punto.
foo

8
@foo: gli interi firmati devono diventare grandi prima di causare problemi. Al contrario, in C, si possono avere problemi a confrontare qualsiasi numero intero negativo - anche - con -1qualsiasi quantità non firmata - anche zero.
supercat

È un peccato che Java non abbia potuto includere tipi senza segno, ma con un set limitato di conversioni e operatori misti (un po 'analogo al modo in cui in C si possono aggiungere 5 a un puntatore, ma non si può confrontare un puntatore a 5) . L'idea che l'uso di un operatore su tipi misti quando esiste un cast implicito, dovrebbe forzare l'uso implicito di quel cast (e usare il tipo conseguente come tipo di risultato) è al centro di molte decisioni di progettazione dubbie sia in .NET che Giava.
supercat

4
Non invocare la tua risposta, ma avere -1un'età "sconosciuta" (come suggerisce l'articolo) è uno dei classici esempi di "odore di codice" . Ad esempio, se vuoi calcolare "quanto Alice è più vecchia di Bob?" E A = 25 e B = -1, otterrai una risposta ±26che è semplicemente sbagliata. La corretta gestione di valori sconosciuti è una specie di Option<TArg>quando Some(25) - Nonesarebbe tornato None.
bytebuster,

11

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 si adattano int, usa long. Se non si adattano longall'uso 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/ bytee smetti di preoccuparti del bit di segno, tranne divisione, confronto, spostamento a destra e casting.

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


5
Sì, per cambiare a destra devi scegliere tra >>e >>>per firmato e non firmato, rispettivamente. Spostare a sinistra non è un problema.
Starblue,

1
@starblue In realtà >>>non funziona per shorte byte. Ad esempio, (byte)0xff>>>1rendimenti 0x7fffffffanziché 0x7f. Un altro esempio: byte b=(byte)0xff; b>>>=1;si tradurrà in b==(byte)0xff. Ovviamente puoi farlo b=(byte)(b & 0xff >> 1);ma questo aggiunge un'altra operazione (bit per bit &).
CITBL,

7
"... Anche con il modello semplificato, la maggior parte dei programmatori Java non sa come si comportano i tipi numerici di base ..." Qualcosa in me risente solo di un linguaggio rivolto al minimo comune denominatore.
Base

La linea di apertura nella tua risposta, su più complicazioni e scarso guadagno, è esattamente ciò che ho elaborato nel mio articolo 6 anni dopo: nayuki.io/page/unsigned-int-considered-harmful-for-java
Nayuki

1
@Nayuki Il tuo articolo è davvero carino. Solo una piccola osservazione, userei l'aggiunta di 0x80000000 per gli operatori di confronto anziché XOR, perché spiega perché funziona, sposta la regione contigua in cui si verifica il confronto da -MAXINT a 0. A bit, il suo effetto è esattamente lo stesso.
Starblue,

6

Con JDK8 ha qualche supporto per loro.

Potremmo ancora vedere il pieno supporto di tipi senza segno in Java nonostante le preoccupazioni di Gosling.


12
alias "Quindi la gente la usa davvero e abbiamo sbagliato a non includerla per cominciare" - ma non ci fidiamo ancora che gli sviluppatori Java sappiano se una variabile è firmata o no - quindi non le implementeremo nella VM o come tipi equivalenti ai loro cugini firmati.
Base

6

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


4

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.


Questo andrebbe bene ... tranne che l'evidenza dell'intervista di Gosling implica che numeri interi senza segno (a parte char) sono stati esclusi perché i progettisti pensavano che fossero una cattiva idea ... dati gli obiettivi del linguaggio.
Stephen C,

È una buona idea non dare mai troppo valore alle dichiarazioni dei testimoni oculari, se anche prove documentali sono a portata di mano.
user7610

4

Una volta ho seguito un corso C ++ con qualcuno nel comitato per gli standard C ++ che mi ha insinuato 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 eseguire il debug come l'overflow 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. Anche,

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 firmati vs byte non firmati o pantaloncini 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.


2

La tua domanda è "Perché Java non supporta gli integ non firmati"?

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

Quindi i metodi statici suppongono di essere le operazioni senza segno anche per 32 e 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 potrebbe aiutarti.

A mio parere, Java è non è simile a C ++ a tutti , se non supporta i tipi senza segno l'overloading degli operatori, quindi penso che Java deve essere trattato come linguaggio completamente diverso sia da C ++ e da 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 potrai fare quello che vuoi fare in C ++, cioè il codice non continuerà ad essere C ++ come per niente e per me è un male codificare in quel 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 per operazioni firmate e non firmate, a meno che non siano necessarie solo operazioni firmate nel codice, e va bene utilizzare solo gli operatori.

Inoltre consiglio di evitare l'uso di tipi primitivi short , int e long e di utilizzare rispettivamente word , dword e qword , e si sta 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, va bene usare questi tipi primitivi short , int e long .

In realtà la parola , DWORD e QWORD non non esiste nella lingua, ma è possibile creare nuova classe per ciascuno e per l'attuazione di ogni dovrebbe essere molto semplice:

La classe di parola detiene il tipo primitivo breve solo, la classe DWORD detiene il tipo primitivo int solo e la classe QWORD detiene il tipo primitivo lungo solo. 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 significativi sulla classe di parole , tutte le operazioni a 32 bit sia senza segno che con firmato dando nomi di significato sulla classe dword e tutte le operazioni a 64 bit sia senza segno che firmato 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!

Se desideri metodi anziché operatori per operazioni con segno a 8 bit e metodi per operazioni senza segno a 8 bit che non hanno alcun operatore, puoi creare la classe Byte (nota che la prima lettera 'B' è maiuscola, quindi questa non è la byte di tipo primitivo ) e implementare i metodi in questa classe.

Informazioni sul passaggio per valore e passaggio per riferimento:

Se non sbaglio, come in C #, gli oggetti primitivi vengono passati per valore in modo naturale, ma gli oggetti di classe vengono passati per riferimento in modo naturale, quindi ciò significa che gli oggetti di tipo Byte , word , dword e qword verranno passati per riferimento e non per valore per impostazione predefinita. Vorrei che Java avesse oggetti struct come C #, quindi tutti i byte , word , dword e qword potevano essere implementati per essere strutt invece che di classe, quindi per impostazione predefinita sono stati passati per valore e non per riferimento per impostazione predefinita, come qualsiasi oggetto struct in C #, come i tipi primitivi, vengono passati per valore e non per riferimento per impostazione predefinita, ma poiché Java è peggiore di C # e abbiamo per far fronte a questo, allora ci sono solo classi e interfacce, che vengono passate per riferimento e non per valore per impostazione predefinita. Quindi, se vuoi passare oggetti Byte , word , dword e qword per valore e non per riferimento, come qualsiasi altro oggetto di classe in Java e anche in C #, dovrai semplicemente usare il costruttore di copie e il gioco è fatto.

Questa è l'unica soluzione a cui posso pensare. Vorrei solo poter digitare i tipi primitivi in ​​word, dword e qword, ma Java non supporta typedef né utilizza affatto, diversamente da C # che supporta l' utilizzo , che equivale al typedef di C.

Informazioni sull'output:

Per la stessa sequenza di bit , puoi stamparli in molti modi: come binario, come decimale (come il significato di% u in C printf), come ottale (come il significato di% o in C printf), come esadecimale (come il significato di% x in C printf) e come numero intero (come il significato di% d in C printf).

Si noti che C printf non conosce il tipo di variabili che vengono passate come parametri alla funzione, quindi printf conosce il tipo di ogni variabile solo dall'oggetto char * passato al primo parametro della funzione.

Quindi in ciascuna delle classi: byte , word , dword e qword , è possibile implementare il metodo di stampa e ottenere la funzionalità di printf, anche se il tipo primitivo della classe è firmato, è comunque possibile stamparlo come non firmato seguendo alcuni algoritmi che coinvolgono operazioni logiche e di spostamento per far stampare le cifre sull'output.

Sfortunatamente il link che ti ho fornito non mostra come implementare questi metodi di stampa, ma sono sicuro che puoi cercare su Google gli algoritmi necessari per implementare questi metodi di stampa.

Questo è tutto ciò che posso rispondere alla tua domanda e suggerirti.


MASM (assemblatore Microsoft) e Windows definiscono BYTE, WORD, DWORD, QWORD come tipi senza segno. Per MASM, SBYTE, SWORD, SDWORD, SQWORD sono i tipi firmati.
rcgldr,

1

Perché il unsignedtipo è puro male.

Il fatto che in C unsigned - intproduca 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 senza segno, size_tl'intera espressione viene n - (rays.size() - 1) / 2valutata come unsigned. Quell'espressione vuole essere una posizione firmata del nraggio dal medio: il primo raggio dal medio sul lato sinistro avrebbe la posizione -1, il primo sulla destra avrebbe la posizione +1, ecc. Dopo prendendo il valore degli addominali e moltiplicandolo per l' deltaangolo otterrei l'angolo tra nil raggio th e quello medio.

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 doublesigillato il bug.

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


L'aggiunta di "unsigned long" a Java sarebbe scomoda. L'aggiunta di tipi non firmati più piccoli, tuttavia, non avrebbe comportato alcun problema. Soprattutto i tipi più piccoli di "int" avrebbero potuto essere facilmente gestiti facendoli promuovere a "int" in modo numericamente ovvio, e "unsigned int" avrebbe potuto essere gestito dicendo che le operazioni che coinvolgono un int firmato e un int non firmato promuoveranno entrambi gli operandi a "long". L'unica situazione problematica sarebbero le operazioni che coinvolgono un lungo senza segno e una quantità firmata, poiché non vi sarebbe alcun tipo in grado di rappresentare tutti i valori di entrambi gli operandi.
Supercat,

@supercat: se unsignedviene convertito intin ogni operazione a che serve unsigned? Non avrà alcuna funzionalità distinguibile da short. E se ti converti intsolo in operazioni miste, come unsigned+into unsigned+float, allora hai ancora il problema di ((unsigned)25-(unsigned)30)*1.0 > 0, che è una delle principali cause di unsignedbug correlati.
Michael,

Molte operazioni su tipi senza segno promuoveranno a "lungo". La richiesta di cast espliciti quando si memorizza il risultato in tipi non firmati causerebbe più o meno gli stessi fastidi esistenti con short e byte, ma se il tipo è principalmente un formato di archiviazione piuttosto che un formato di calcolo che non dovrebbe essere un problema. In ogni caso, i tipi senza segno più brevi di "int" dovrebbero semplicemente essere in grado di promuovere senza difficoltà "int".
supercat,

3
Non mi piace questa risposta perché usa l'argomento "numeri interi senza segno sono cattivi e non dovrebbero esistere perché non possono mai essere firmati". Chiunque cerchi di sottrarre da un numero intero senza segno dovrebbe saperlo già. E per quanto riguarda la leggibilità, C non è esattamente noto per essere facile da seguire. Inoltre, anche l'argomento (semi) "il bit in più non vale il guaio in più" è molto debole. La gestione degli errori invece di exit(1);"vale davvero il disturbo extra"? Poter aprire file di grandi dimensioni non vale davvero la sicurezza che i programmatori Java meno esperti non rovineranno unsigned?
yyny,

2
L'unica cosa cattiva che vedo in questo codice è n - (rays.size() - 1) / 2. Dovresti sempre mettere tra parentesi gli operatori binari perché il lettore del codice non dovrebbe assumere nulla sull'ordine delle operazioni in un programma per computer. Solo perché convenzionalmente diciamo a + b c = a + (b c) non significa che si può assumere questo durante la lettura del codice. Inoltre, il calcolo dovrebbe essere definito all'esterno del loop in modo che possa essere testato senza il loop presente. Questo è un bug nel non assicurarsi che i tuoi tipi si allineino piuttosto che un problema di numeri interi senza segno. In C spetta a te assicurarsi che i tuoi tipi siano allineati.
Dmitry,

0

Ci sono alcune gemme nelle specifiche '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 all'infinito", in modo che tutte le richieste di offset degli indirizzi aggiungessero prima la loro DIMENSIONE ANELLO ALGEBRAICO per ovviare a 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 - nel contesto del "ciclo auto-rotante"

// ...
// 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 ci avesse provato. Ricorda, ci sono anche molte richieste legittime per chiedere valori indice precedenti (negativi).

NB: il modulo% esterno rimanda 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: Giusto per dare un contesto alla precedente pulizia RingArray, ecco un'operazione candidata 'set' per abbinare l'operazione sopra 'get' dell'elemento:

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
}

-2

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


1
Probabilmente sta pensando ad array e non è in grado di usare numeri interi negativi come indici. Probabilmente.
SK9

2
Quando i campi di incremento automatico nei database traboccano, spesso vanno fuori di testa.
Giosuè

-8

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 questa lingua o stai semplicemente parlando fuori di testa. ;)

quando ti occupi quotidianamente del formato, sia dal file che dall'hardware, inizi a mettere in discussione cosa diavolo stessero pensando.

Un buon esempio qui sarebbe provare a usare un byte senza segno come un loop auto-rotante. Per quelli di voi che non comprendono l'ultima frase, come mai ti definisci programmatore.

DC


34
Solo per i calci, Google la frase "anello auto-rotante". Chiaramente , Denis Co è l'unica persona al mondo degna di definirsi un programmatore :-)
Stephen C

6
Questa risposta è così negativa che è divertente
Nayuki,
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.