web-dev-qa-db-ja.com

Javaが符号なしintをサポートしないのはなぜですか?

Javaに符号なし整数のサポートが含まれないのはなぜですか?

予想外に大きな入力でオーバーフローが発生する可能性が低いコードを作成できるため、奇妙な省略に思えます。

さらに、符号なし整数の使用は、自己署名の形式になります。これは、符号なし整数が保持することを意図した値が負になることはないことを示すためです。

最後に、場合によっては、除算などの特定の操作では符号なし整数の方が効率的です。

これらを含めることのマイナス面は何ですか?

358
dsimcha

これは Goslingなどとのインタビュー からのもので、シンプルさについてです:

ゴスリング:言語デザイナーとしての私にとって、最近は本当に自分自身を数えていませんが、「シンプル」が本当に意味を成したのは、J。Random Developerが彼の仕様を保持することでした。その定義によれば、たとえば、Javaはそうではありません。実際、これらの言語の多くは、実際には誰も理解していない多くのコーナーケースを抱えています。符号なしのC開発者をクイズすると、符号なしで何が起こっているのか、符号なし算術とは何かを実際に理解しているC開発者はほとんどいないことがすぐにわかります。そのようなことがCを複雑にしました。 Javaの言語部分は、かなり単純だと思います。検索する必要のあるライブラリ。

185
Uri

行間を読むと、ロジックは次のようなものだったと思います。

  • 一般的に、Java設計者は利用可能なデータ型のレパートリーを単純化したかった
  • 日常的な目的のために、彼らは最も一般的なニーズは署名されたデータ型であると感じました
  • 特定のアルゴリズムを実装するために、符号なし算術が必要になる場合がありますが、そのようなアルゴリズムを実装するプログラマーは、符号付きデータ型で符号なし算術を実行する「作業」の知識も持っています。

ほとんどの場合、それは合理的な決定だったと思います。おそらく、私は持っているでしょう:

  • バイトを符号なしにするか、少なくともこの名前のデータ型に対して、おそらく異なる名前の符号付き/符号なしの代替を提供しました(署名することは一貫性のために良いですが、いつ符号付きバイトが必要になりますか?)
  • 「short」を廃止しました(最後に16ビット符号付き算術を使用したのはいつですか?)

それでも、少し気を遣うと、最大32ビットの符号なしの値に対する操作は悪くありません。また、ほとんどの人は符号なしの64ビットの除算や比較を必要としません。

50
Neil Coffey

これは古い質問であり、patはcharについて簡単に言及しましたが、今後これを見てくれる他の人のためにこれを拡張すべきだと思いました。 Javaプリミティブ型を詳しく見てみましょう。

byte-8ビット符号付き整数

short-16ビット符号付き整数

int-32ビット符号付き整数

long-64ビット符号付き整数

char-16ビット文字(符号なし整数)

charunsigned算術演算をサポートしていませんが、本質的にunsigned整数として扱うことができます。算術演算を明示的にcharにキャストし直す必要がありますが、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--;

はい、符号なし整数を直接サポートしていません(明らかに、直接サポートがあれば、ほとんどの操作をcharにキャストする必要はありません)。ただし、符号なしのプリミティブデータ型は確かに存在します。符号なしバイトも見たかったのですが、メモリコストを2倍にして、代わりにcharを使用することは実行可能なオプションです。


編集

JDK8には、 Long および Integer の新しいAPIがあり、longおよびintの値を符号なしの値として扱うときにヘルパーメソッドを提供します。

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

さらに、 Guava は、整数型で同様のことを行うヘルパーメソッドをいくつか提供し、unsigned整数のネイティブサポートの欠如によって残されたギャップを埋めるのに役立ちます。

18
Jyro117

Javaには符号なしの型、または少なくとも1つがあります。charは符号なしのshortです。そのため、Goslingが言い訳をしたとしても、他に符号なしの型が存在しないのは、彼の無知にすぎません。

また、ショートタイプ:マルチメディアには常にショートパンツが使用されます。理由は、2つのサンプルを単一の32ビット符号なしlongに収め、多くの演算をベクトル化できるからです。 8ビットデータと符号なしバイトでも同じです。ベクトル化のためにレジスタに4つまたは8つのサンプルを収めることができます。

16
pat

式に符号付きと符号なしのintが混在すると、すぐに物事が乱雑になり始め、おそらくwill情報を失います。 Javaを署名されたintに制限すると、事態は本当に明確になります。署名された/署名されていないビジネス全体を心配する必要はありませんが、バイトの8ビット目を見逃すことがあります。

14
Bombe

http://skeletoncoder.blogspot.com/2006/09/Java-tutorials-why-no-unsigned.html

C標準では、符号なしおよび符号付き整数を含む演算を符号なしとして扱うように定義しているため、この男は言います。これにより、負の符号付き整数が大きな符号なし整数に転がり、潜在的にバグを引き起こす可能性がありました。

12
akatakritos

Javaはそのままで良いと思います。符号なしを追加すると、大きな利益なしに複雑になります。単純化された整数モデルを使用しても、ほとんどのJavaプログラマーは基本的な数値型の動作を知らない-本を読むだけで Java Puzzlers あなたが抱くかもしれない誤解を確認します。

実用的なアドバイスとして:

  • 値がいくぶん任意のサイズで、intに収まらない場合は、longを使用します。 longに収まらない場合は、BigIntegerを使用します。

  • スペースを節約する必要がある場合は、配列にのみ小さい型を使用してください。

  • 正確に64/32/16/8ビットが必要な場合は、long/int/short/byteを使用し、除算、比較、右シフト、およびキャストを除いて、符号ビットの心配を停止します。

(CからJavaへの乱数ジェネレーターの移植)に関する this 回答も参照してください。

11
starblue

この投稿は古すぎます。ただし、Java 8以降では、intデータ型を使用して、最小値が0で最大値が2の符号なし32ビット整数を表すことができます。32-1。 Integerクラスを使用して、intデータ型を符号なし整数として使用し、compareUnsigned()divideUnsigned()などの静的メソッドをIntegerクラスに追加して、符号なし整数の算術演算をサポートします。

6
Morteza Adi

JDK8 を使用すると、いくつかのサポートがあります。

Goslingの懸念にもかかわらず、Javaで未署名の型の完全なサポートがまだ見られるかもしれません。

6
John Hascall

元のJavaリリースの近くに含まれるという話を聞いたことがあります。オークはJavaの前身であり、いくつかの仕様書では、価値のある価値について言及されていました。残念ながら、これらはJava言語には決してなりませんでした。誰かが理解できる限り、おそらく時間の制約のために実装されなかっただけです。

4
Rob Ottaway

(1)符号なし整数を使用するほとんどのプログラムは符号付き整数でも同様に実行できるため、Javaが符号なし整数を避ける正しい判断をしたことを暗示したC++標準委員会の誰かとC++コースを受講したことがあります(2)符号なし整数を使用すると、簡単に作成できますが、整数演算オーバーフローや符号付き型と符号なし型の間の変換時に重要なビットが失われるなどのデバッグが困難になります。符号付き整数を使用して誤って0から1を引くと、2 ^ 32-1にラップアラウンドする場合よりもプログラムがクラッシュしやすくなり、バグを見つけやすくなります。コンパイラーと静的分析ツールとランタイムチェックは、符号なし算術を使用することを選択したので、あなたが何をしているかを知っていると仮定します。また、-1のような負の数は、フィールドが無視/デフォルト/設定解除されるなど、有用な何かを表すことがよくありますが、符号なしを使用している場合は、2 ^ 32-1などの特別な値を予約する必要があります。

昔、メモリが制限されていて、プロセッサが一度に64ビットで自動的に動作しなかったとき、すべてのビットがより多くカウントされたため、符号付きまたは符号なしのバイトまたはショートが実際にはるかに重要であり、明らかに正しい設計決定でした。現在、ほとんどすべての通常のプログラミングの場合、符号付き整数を使用するだけで十分であり、プログラムで実際に2 ^ 31-1より大きい値を使用する必要がある場合は、とにかく長い値が必要になります。ロングを使用する領域に入ると、2 ^ 63-1の正の整数では本当にうまくいかない理由を見つけるのがさらに難しくなります。 128ビットプロセッサを使用すると、問題はさらに少なくなります。

3
Jonathan

あなたの質問は、「Javaが符号なしintをサポートしない理由」です。

そして、あなたの質問に対する私の答えは、Javaがすべてのプリミティブ型であることを望んでいるということです。bytecharshort、- intおよびlongは、byteWorddwordおよびqwordそれぞれ、Assemblyとまったく同じです。また、Java演算子はcharを除くすべてのプリミティブ型に対するsigned操作ですが、- char符号なし16ビットのみです。

したがって、静的メソッドは、32ビットと64ビットの両方でnsigned操作alsoであると想定しています。

nsigned操作のために静的メソッドを呼び出すことができる最終クラスが必要です。

この最終クラスを作成し、任意の名前を付けて静的メソッドを実装できます。

静的メソッドの実装方法がわからない場合は、この link が役立つ場合があります。

私の意見では、Javaはnot C++に似ていますすべて、それがあればneither符号なし型をサポートnor演算子のオーバーロード。したがって、JavaはC++とCの両方から完全に異なる言語として扱われるべきだと思います。

ちなみに言語の名前もまったく違います。

したがって、JavaでCに似たコードを入力することはお勧めしません。また、Javaでは何もできないため、C++に似たコードを入力することはお勧めしません。次はC++でやりたい、つまり、コードがまったくC++であり続けることはないので、私にとっては、そのようなコードを作成して、途中でスタイルを変更するのは悪いことです。

署名付き操作にも静的メソッドを記述して使用することをお勧めします。コード内に署名付き操作だけが必要な場合を除き、署名付き操作と署名なし操作の両方に演算子と静的メソッドを混在させないでください。演算子のみを使用してください。

また、shortintおよびlongプリミティブ型の使用を避け、Worddwordおよびqword代わりに、それぞれ、演算子を使用する代わりに、符号なし操作および/または符号付き操作の静的メソッドを呼び出します。

符号付き演算のみを実行し、コード内でのみ演算子を使用する場合、これらのプリミティブ型shortintおよびlong

実際にはWorddwordおよびqword do n'tが言語に存在しますが、新しいクラスを作成できますそれぞれとそれぞれの実装は非常に簡単でなければなりません:

クラスWordはプリミティブ型を保持shortのみ、クラスdwordはプリミティブ型を保持intのみ、クラスqwordはプリミティブ型を保持しますlongのみ。これで、すべての未署名および署名済みメソッドが静的または選択としてではなく、各クラスに実装できます。つまり、Wordクラスに意味の名前を付けることで、すべての16ビット演算を署名なしと署名の両方で実装できますdwordクラスで意味名を指定することによる符号なしと署名の両方の32ビット操作、およびqwordクラスで意味名を指定することによる符号なしと署名の両方の64ビット操作。

各メソッドにあまりにも多くの異なる名前を付けたくない場合は、Javaで常にオーバーロードを使用できます。Java did n'tそれも削除してください!

8ビットの符号付き演算の演算子や、演算子をまったく持たない8ビットの符号なし演算のメソッドではなくメソッドが必要な場合は、Byteクラスを作成できます(最初の文字「B」は大文字であるため、これはプリミティブ型byte)ではなく、このクラスのメソッドを実装します。

値渡しと参照渡しについて:

私が間違っていない場合、C#のように、プリミティブオブジェクトは自然に値で渡されますが、クラスオブジェクトは参照で自然に渡されるため、タイプByteWord =、dwordおよびqwordは、デフォルトでは値ではなく参照によって渡されます。 JavaがC#のようにstructオブジェクトを持っているので、すべてのByteWorddwordおよびqwordは、classではなくstructに実装できるため、デフォルトでは、構造体のように、デフォルトでは参照ではなく値で渡されます。 C#のオブジェクトは、プリミティブ型と同様に、デフォルトでは参照ではなく値で渡されますが、そのJavaはC#よりも悪く、対処する必要があるため、クラスとインターフェイスのみがあります。デフォルトでは値ではなく参照によって渡されます。したがって、ByteWorddword、およびqwordオブジェクトを参照ではなく値で渡す場合JavaおよびC#の他のクラスオブジェクトでは、単にコピーコンストラクタを使用する必要があります。

それが私が考えることができる唯一の解決策です。プリミティブ型をWord、dword、およびqwordにtypedefできることを望みますが、singをサポートするC#とは異なり、Javaはtypedefをサポートせず、まったく使用しません。 Cのtypedef。

出力について:

同じビットのシーケンスに対して、多くの方法でそれらを印刷できます:バイナリとして、10進数として(C printfの%uの意味のように)、8進数として(Cの%oの意味のように) printf)、16進数(C printfの%xの意味など)および整数(C printfの%dの意味)。

Cのprintfは関数にパラメーターとして渡される変数の型を知らないため、printfは関数の最初のパラメーターに渡されたchar *オブジェクトからのみ各変数の型を知っていることに注意してください。

したがって、各クラス:ByteWorddword、およびqwordで、printメソッドを実装してget printfの機能は、クラスのプリミティブ型が署名されている場合でも、論理演算およびシフト操作を含むアルゴリズムに従って出力に印刷する数字を取得することにより、未署名として印刷できます。

残念ながら、私が提供したリンクにはこれらの印刷方法を実装する方法が示されていませんが、これらの印刷方法を実装するために必要なアルゴリズムをグーグルで検索できると確信しています。

あなたの質問に答えて、あなたに提案できるのはそれだけです。

2
user2133061

unsigned型は純粋な悪だからです。

Cでunsigned - intunsignedを生成するという事実は、さらに悪です。

これが私を何度も焼き付けた問題のスナップショットです:

// 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 ...
}

バグにまだ気づいていますか?私は、デバッガーでステップインした後にのみ見たと告白します。

nは符号なしの型size_tであるため、式n - (rays.size() - 1) / 2全体はunsignedとして評価されます。その式は、signed真ん中のnth光線の位置であることが意図されています:左側の真ん中からの最初の光線の位置は-1になり、右側の最初の光線には位置があります位置+1など。abs値を取得し、delta角度で乗算した後、nth光線と中央の光線の間の角度を取得します。

残念ながら、上記の式には符号なしの悪が含まれており、たとえば-1と評価される代わりに、2 ^ 32-1と評価されました。その後のdoubleへの変換により、バグが封印されました。

unsigned算術演算の誤用によって引き起こされた1つまたは2つのバグの後、余分なビットを取得することは余分なトラブルの価値があるかどうか疑問に思う必要があります。可能な限り、算術でunsigned型を使用しないようにしていますが、バイナリマスクなどの非算術演算には引き続き使用しています。

1
Michael

'C'仕様には、実用的な理由でJavaが削除されたが、開発者の要求(クロージャなど)とともに徐々に忍び寄る宝石がいくつかあります。

この議論に関連しているので、最初のものについて言及します。ポインタ値の符号なし整数演算への準拠。そして、このスレッドのトピックに関連して、Javaの署名付きの世界で署名なしのセマンティクスを維持することの難しさ。

デニスリッチーの代替エゴを入手して、ゴズリングの設計チームに「無限大でゼロ」を与えることを提案した場合、すべてのアドレスオフセットリクエストが最初にALGEBRAIC RING SIZEを追加して負の値を回避することを提案したと思います。

そのようにして、配列にスローされたオフセットがSEGFAULTを生成することはありません。たとえば、「自己回転ループ」コンテキストで、符号なしの動作を必要とするdoubleのRingArrayを呼び出すカプセル化されたクラスの場合:

// ...
// 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は、悪意のあるリクエスターが試みたとしても、負のインデックスから「取得」することはありません。また、以前の(負の)インデックス値を要求するための正当なリクエストも数多くあります。

NB:外側の%modulusは正当な要求を逆参照しますが、内側の%modulusは-modulusよりも負の否定から露骨な悪意を隠します。これがJava + .. + 9 ||に表示される場合8 + .. +仕様の場合、問題は真に「故障を「自己回転」できないプログラマ」になります。

いわゆるJava unsigned int 'deficiency'は、上記のワンライナーで補うことができると確信しています。

PS:上記のRingArray Housekeepingにコンテキストを与えるために、上記の「get」要素操作に一致する「set」操作の候補を次に示します。

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
}
0
MKhomo