Pourquoi Java n'inclut-il pas la prise en charge des entiers non signés?
Cela me semble être une étrange omission, dans la mesure où ils permettent d'écrire du code moins susceptible de produire des débordements sur une entrée d'une taille inattendue.
De plus, utiliser des entiers non signés peut être une forme d'auto-documentation, car ils indiquent que la valeur que l'entité non signée était censée contenir n'est jamais supposée être négative.
Enfin, dans certains cas, les entiers non signés peuvent être plus efficaces pour certaines opérations, telles que la division.
Quel est l'inconvénient d'inclure ceux-ci?
Ceci est tiré d'un entretien avec Gosling et d'autres , à propos de simplicité:
Gosling: Pour moi en tant que concepteur de langage, ce que je ne me considère pas vraiment comme de nos jours, ce que "simple" a vraiment voulu dire était de savoir si je pouvais m'attendre à ce que J. Random Developer garde la spécification en tête. Selon cette définition, par exemple, Java ne l’est pas - et en fait, beaucoup de ces langages se retrouvent avec de nombreux cas de figure, des choses que personne ne comprend vraiment. Interrogez n'importe quel développeur C sur les non-signés et vous découvrirez bientôt que presque aucun développeur C ne comprend réellement ce qui se passe avec l'arithmétique non-signée, non signée. Des choses comme ça ont rendu C complexe. La partie langage de Java est, je pense, assez simple. Les bibliothèques que vous devez rechercher.
En lisant entre les lignes, je pense que la logique ressemblait à ceci:
La plupart du temps, je dirais que c'était une décision raisonnable. Peut-être que j'aurais:
Néanmoins, avec un peu de kludging, les opérations sur des valeurs non signées jusqu'à 32 bits ne sont pas trop mauvaises, et la plupart des gens n'ont pas besoin de division ou de comparaison 64 bits non signée.
Ceci est une question plus ancienne et pat a brièvement mentionné l'omble, j'ai juste pensé que je devrais développer cela pour ceux qui examineront cela plus tard. Examinons de plus près les types primitifs Java:
byte
- entier signé de 8 bits
short
- entier signé de 16 bits
int
- entier signé 32 bits
long
- entier signé 64 bits
char
- caractère 16 bits (entier non signé)
Bien que char
ne prenne pas en charge l’arithmétique unsigned
, il peut être traité comme un entier unsigned
. Vous devez explicitement convertir les opérations arithmétiques en char
, mais cela vous fournit un moyen de spécifier des nombres unsigned
.
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--;
Oui, il n'y a pas de support direct pour les entiers non signés (évidemment, je n'aurais pas à redonner la plupart de mes opérations en caractère s'il y avait un support direct). Cependant, il existe certainement un type de données primitif non signé. J'aurais aussi aimé voir un octet non signé, mais j'imagine que doubler le coût de la mémoire et utiliser plutôt un caractère est une option viable.
Avec JDK8, de nouvelles API pour Long
et Integer
fournissent des méthodes d'assistance. lors du traitement des valeurs long
et int
en tant que valeurs non signées.
compareUnsigned
divideUnsigned
parseUnsignedInt
parseUnsignedLong
remainderUnsigned
toUnsignedLong
toUnsignedString
De plus, Guava fournit un certain nombre de méthodes d’aide permettant de faire la même chose pour les types entiers, ce qui permet de combler l’écart laissé par le manque de prise en charge native de unsigned
. entiers.
Java a des types non signés, ou au moins un: char est un short non signé. Quelle que soit l'excuse que Gosling avance, c'est simplement son ignorance qu'il n'y a pas d'autres types non signés.
Aussi les types courts: les courts métrages sont utilisés tout le temps pour le multimédia. La raison en est que vous pouvez adapter 2 échantillons dans un seul long non signé 32 bits et vectoriser de nombreuses opérations. Même chose avec les données 8 bits et l'octet non signé. Vous pouvez ajuster 4 ou 8 échantillons dans un registre pour la vectorisation.
Dès que les expressions signée et non signée sont mélangées dans une expression, les choses commencent à devenir compliquées et vous perdrez probablement des informations. Restreindre Java aux entrées signées ne fait qu'éclaircir les choses. Je suis heureux de ne pas avoir à s’inquiéter de toute l’activité signée/non signée, même si je manque parfois le 8ème bit dans un octet.
http://skeletoncoder.blogspot.com/2006/09/Java-tutorials-why-no-unsigned.html
Ce type dit que parce que la norme C définit les opérations impliquant des ints non signés et signés à traiter comme non signés. Cela pourrait faire rouler des entiers signés négatifs dans un grand entier non signé, ce qui pourrait causer des bogues.
Je pense que Java est correct, ajouter un signe non signé le compliquerait sans grand gain. Même avec le modèle entier simplifié, la plupart des Java programmeurs ne savent pas comment se comportent les types numériques de base - il suffit de lire le livre Java Puzzlers pour voir quelles sont les idées fausses que vous pourriez avoir.
Pour des conseils pratiques:
Si vos valeurs ont une taille quelque peu arbitraire et ne rentrent pas dans int
, utilisez long
. S'ils ne rentrent pas dans long
, utilisez BigInteger
.
Utilisez les types les plus petits uniquement pour les tableaux lorsque vous devez économiser de l'espace.
Si vous avez besoin de 64/32/16/8 bits exactement, utilisez long
/int
/short
/byte
et arrêtez de vous préoccuper du bit de signe, sauf pour la division, la comparaison , décalage à droite et casting.
Voir aussi this réponse sur "porter un générateur de nombres aléatoires de C en Java".
Je sais que ce post est trop vieux; toutefois, pour votre intérêt, dans Java 8 et versions ultérieures, vous pouvez utiliser le type de données int
pour représenter un entier non signé de 32 bits, qui a une valeur minimale de 0 et une valeur maximale de 2.32−1. Utilisez la classe Integer
pour utiliser le type de données int
en tant qu'entier non signé et des méthodes statiques telles que compareUnsigned()
, divideUnsigned()
etc. ont été ajoutées à la classe Integer
supporte les opérations arithmétiques pour les entiers non signés.
Avec JDK8 , il est pris en charge.
Nous pouvons encore voir un support complet des types non signés dans Java malgré les préoccupations de Gosling.
J'ai entendu dire qu'ils devaient être inclus près de la version originale Java. Oak était le précurseur de Java et, dans certains documents de spécification, il était fait mention de valeurs usignées. Malheureusement, ceux-ci n’ont jamais été intégrés au langage Java. Pour autant que quiconque ait pu le constater, il n’a tout simplement pas été mis en œuvre, probablement en raison de contraintes de temps.
Une fois, j'ai suivi un cours de C++ avec un membre du comité de normalisation C++ qui a laissé entendre que Java avait pris la bonne décision pour éviter d'avoir des entiers non signés car (1) la plupart des programmes utilisant des entiers non signés peuvent tout aussi bien fonctionner avec des entiers signés. et ceci est plus naturel en termes de pensée, et (2) utiliser des entiers non signés résulte en beaucoup de choses faciles à créer mais difficiles à résoudre, comme le débordement arithmétique d’entiers et la perte de bits significatifs lors de la conversion entre types signés et non signés. Si par erreur vous soustrayez 1 de 0 en utilisant des entiers signés, votre programme se bloque plus rapidement et rend plus facile la recherche du bogue que si le problème se ramène à 2 ^ 32 - 1, et les compilateurs, les outils d'analyse statique et les vérifications d'exécution doivent supposez que vous savez ce que vous faites depuis que vous avez choisi d'utiliser l'arithmétique non signée. De plus, les nombres négatifs tels que -1 peuvent souvent représenter quelque chose d’utile, comme un champ ignoré/par défaut/non défini tandis que si vous utilisiez un non signé, vous devez réserver une valeur spéciale telle que 2 ^ 32 - 1 ou quelque chose de similaire.
Il y a bien longtemps, lorsque la mémoire était limitée et que les processeurs ne fonctionnaient pas automatiquement sur 64 bits à la fois, chaque bit comptait beaucoup plus. Avoir signé vs octets ou shorts non signés importait en réalité beaucoup plus souvent et constituait évidemment la bonne décision de conception. Aujourd’hui, l’utilisation d’un entier signé est largement suffisante dans la plupart des cas de programmation classiques, et si votre programme doit vraiment utiliser des valeurs supérieures à 2 ^ 31 - 1, vous voulez souvent un long. Une fois que vous êtes sur le terrain de l'utilisation de longs, il est encore plus difficile de trouver une raison pour laquelle vous ne pouvez vraiment pas vous en tirer avec 2 entiers positifs 63 ~ 1. Chaque fois que nous allons aux processeurs 128 bits, ce sera encore moins un problème.
Votre question est "Pourquoi Java ne supporte-t-il pas les unités non signées"?
Et ma réponse à votre question est que Java veut que tous ses types primitifs: octet , char , court , int et long doivent être traités comme octet , Word , dword et qword respectivement, exactement comme dans Assembly, et les opérateurs Java sont signés sur tous ses types primitifs à l'exception de pour char , mais uniquement sur char , ils ne sont pas signés sur 16 bits.
Les méthodes statiques supposent donc que les opérations unsigned soient également pour 32 et 64 bits.
Vous avez besoin de la classe finale, dont les méthodes statiques peuvent être appelées pour les opérations unsigned .
Vous pouvez créer cette classe finale, l'appeler comme vous le souhaitez et implémenter ses méthodes statiques.
Si vous ne savez pas comment implémenter les méthodes statiques, ceci link peut vous aider.
À mon avis, Java est pas du tout similaire à C++ du tout , s'il ni ne prend en charge les types non signés ni la surcharge de l'opérateur , je pense donc que Java devrait être traité comme un langage complètement différent du C++ et du C.
C'est d'ailleurs complètement différent au nom des langues.
Donc, je ne recommande pas dans Java de taper du code similaire à C et je ne recommande pas de saisir du code similaire à C++, car dans Java vous ne serez pas capable de faire ce que vous voulez faire ensuite en C++, c’est-à-dire que le code ne continuera pas à être du C++, et pour moi, c’est mauvais de coder comme ça, de changer le style au milieu.
Je recommande d’écrire et d’utiliser des méthodes statiques également pour les opérations signées. Vous ne verrez donc pas dans le code une combinaison d’opérateurs et de méthodes statiques pour les opérations signées et non signées, sauf si vous n’avez besoin que d’opérations signées dans le code. utilisez uniquement les opérateurs.
Aussi, je recommande d'éviter d'utiliser court , int et long types primitifs, et utilisation Word , dword et qword respectivement et appelez les méthodes statiques pour les opérations non signées et/ou signées au lieu de en utilisant des opérateurs.
Si vous êtes sur le point de n'effectuer que des opérations signées et d'utiliser les opérateurs uniquement dans le code, vous pouvez utiliser ces types primitifs short , int et long .
En fait Word , dword et qword do n’existe pas dans le langage, mais vous pouvez créer une nouvelle classe pour chacun et la mise en oeuvre de chacun devrait être très facile:
La classe Word contient le type primitif court uniquement, la classe dword contient le type primitif int uniquement et la classe qword contient le type primitif long uniquement. Désormais, toutes les méthodes non signées et signées, statiques ou non, peuvent être implémentées dans chaque classe, c’est-à-dire toutes les opérations 16 bits non signées et signées en donnant des noms de signification dans le mot ( classe, toutes les opérations 32 bits non signées et signées en donnant des noms de sens à la classe dword et à toutes les 64 bits opérations non signées et signées en donnant des noms de sens à la classe qword .
Si vous n'aimez pas donner trop de noms différents pour chaque méthode, vous pouvez toujours utiliser la surcharge en Java, il est bon de lire que Java n'a pas n't supprime ça aussi!
Si vous souhaitez des méthodes plutôt que des opérateurs pour les opérations signées 8 bits et des méthodes pour les opérations non signées 8 bits sans opérateur, vous pouvez créer l'octet class (notez que la première lettre 'B' est majuscule, ce n'est donc pas le type primitif byte ) et implémentez les méthodes de cette classe.
À propos de passer par valeur et de passer par référence:
Si je ne me trompe pas, comme en C #, les objets primitifs sont passés naturellement par la valeur, mais les objets de classe sont passés naturellement par la référence, ce qui signifie que les objets de type Byte , Word , dword et qword sera passé par référence et non par valeur par défaut. Je souhaite que Java ait des objets struct identiques à ceux de C #, donc tous octets , Word , dword et qword pourrait être implémenté pour être struct au lieu de class , donc, par défaut, ils ont été passés par valeur et non par référence, comme tout objet struct en C #, comme les types primitifs, sont passés par valeur et non par référence, mais parce que Java est pire que C # et que nous devons en tenir compte, il n’existe alors que des classes et des interfaces, qui sont transmises par référence et non par valeur. Donc, si vous voulez passer Byte , Word , dword et qword objets par valeur et non par référence, comme tout autre objet de classe dans Java et aussi en C #, vous devrez simplement utiliser le constructeur de copie et le tour est joué.
C'est la seule solution à laquelle je peux penser. J'aimerais seulement pouvoir typer les types primitifs dans Word, dword et qword, mais Java ne prend en charge ni typedef ni l'utilisation du tout, contrairement à C # qui supporte en utilisant , ce qui équivaut à la typedef du C.
A propos de la sortie:
Pour la même séquence de bits , vous pouvez les imprimer de différentes manières: en tant que binaire, en tant que décimal (comme la signification de% u dans C printf), en octal (comme la signification de% o dans C printf), en hexadécimal (comme la signification de% x dans C printf) et en tant qu'entier (comme la signification de% d dans C printf).
Notez que C printf ne connaît pas le type des variables transmises en tant que paramètres à la fonction. Par conséquent, printf connaît le type de chaque variable uniquement à partir de l'objet char * transmis au premier paramètre de la fonction.
Donc, dans chacune des classes: Byte , Word , dword et qword , vous pouvez implémenter une méthode d'impression et obtenir les fonctionnalités de printf, même bien que le type primitif de la classe soit signé, vous pouvez toujours l’imprimer comme non signée en suivant un algorithme impliquant des opérations logiques et des opérations de décalage pour obtenir les chiffres à imprimer vers la sortie.
Malheureusement, le lien que je vous ai donné ne montre pas comment implémenter ces méthodes d'impression, mais je suis sûr que vous pouvez rechercher sur Google les algorithmes dont vous avez besoin pour implémenter ces méthodes d'impression.
C'est tout ce que je peux répondre à votre question et vous suggérer.
Parce que unsigned
le type est un pur mal.
Le fait que dans C unsigned - int
produise unsigned
est encore plus pervers.
Voici un aperçu du problème qui m'a brûlé plus d'une fois:
// 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 ...
}
Avez-vous déjà remarqué le bug? J'avoue que je ne l'ai vu qu'après être intervenu avec le débogueur.
Parce que n
est de type non signé size_t
l'expression entière n - (rays.size() - 1) / 2
est évaluée comme unsigned
. Cette expression est censée être un signé la position du n
rayon du centre: le premier rayon du centre à gauche aurait la position -1, le premier sur la droite aurait la position +1, etc. Après avoir pris la valeur abs et multiplié par l'angle delta
, j'obtiendrais l'angle entre n
thray et le rayon médian.
Malheureusement pour moi, l'expression ci-dessus contenait le mal non signé et au lieu d'évaluer -1, par exemple, elle était évaluée à 2 ^ 32-1. La conversion ultérieure en double
a scellé le bogue.
Après un ou deux bugs causés par une mauvaise utilisation de l'arithmétique unsigned
, il faut commencer à se demander si le peu supplémentaire que l'on obtient vaut la peine d'être compensé. J'essaie, autant que possible, d'éviter toute utilisation de types unsigned
en arithmétique, bien que je l'utilise quand même pour des opérations non arithmétiques telles que les masques binaires.
La spécification 'C' contient quelques joyaux que Java a abandonné pour des raisons pragmatiques, mais qui recule lentement avec la demande des développeurs (fermetures, etc.).
Je mentionne un premier parce qu'il est lié à cette discussion; l'adhérence des valeurs de pointeur à l'arithmétique entière non signée. Et, par rapport à ce sujet de fil, la difficulté de maintenir la sémantique Unsigned dans le monde signé de Java.
J'imagine que si un alter ego de Dennis Ritchie était conseillé à l'équipe de concepteurs de Gosling, il aurait suggéré de donner à Signed un "zéro à l'infini", de sorte que toutes les demandes de décalage d'adresse ajoutent d'abord leur TAILLE DE SONNERIE ALGÉBRE afin d'éviter les valeurs négatives.
De cette manière, aucun décalage jeté sur le tableau ne peut jamais générer un SEGFAULT. Par exemple, dans une classe encapsulée que j'appelle RingArray, il faut un comportement non signé dans Double, dans un contexte de "boucle rotative automatique":
// ...
// 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
// ...
RingArray ci-dessus ne "obtiendrait" jamais un index négatif, même si un demandeur malveillant tentait de le faire. N'oubliez pas qu'il existe également de nombreuses demandes légitimes de demandes de valeurs d'index antérieures (négatives).
NB: le% module externe dé-référence les requêtes légitimes, tandis que le module interne masque la malveillance flagrante aux négatifs plus négatifs que le module. Si cela devait apparaître dans un Java + .. + 9 || 8 + .. + spec, alors le problème deviendrait véritablement un "programmeur qui ne peut pas" faire pivoter "FAULT".
Je suis sûr que la soi-disant Java unsigned int 'déficience' peut être compensée par le one-liner ci-dessus.
PS: Juste pour donner un contexte à RingArray Housekeeping ci-dessus, voici une opération 'set' candidate correspondant à l'opération d'élément 'get' ci-dessus:
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
}