תמיכה בחשבון Java 8 לא חתום

1. סקירה כללית

משחר ג'אווה, כל סוגי הנתונים המספריים חתומים. אולם במצבים רבים נדרש להשתמש בערכים לא חתומים. לדוגמא, אם אנו סופרים את מספר המופעים של אירוע, איננו רוצים להיתקל בערך שלילי.

התמיכה בחשבון לא חתום הייתה סוף סוף חלק מה- JDK החל מגרסה 8. תמיכה זו הגיעה בצורה של ממשק ה- API שלם ללא חתום, המכיל בעיקר שיטות סטטיות ב- מספר שלם ו ארוך שיעורים.

במדריך זה נעבור על ה- API הזה וניתן הוראות כיצד להשתמש נכון במספרים לא חתומים.

2. ייצוגים ברמת הסיביות

כדי להבין כיצד לטפל במספרים חתומים ולא חתומים, בואו נסתכל תחילה על הייצוג שלהם ברמת הסיביות.

בג'אווה מספרים מקודדים באמצעות מערכת המשלים של השניים. קידוד זה מיישם פעולות חשבון בסיסיות רבות, כולל חיבור, חיסור וכפל, באותו אופן, בין אם האופרטים חתומים ובין אם לא חתומים.

הדברים צריכים להיות ברורים יותר עם דוגמת קוד. לשם הפשטות נשתמש במשתנים של ה- בתים סוג נתונים פרימיטיבי. פעולות דומות עבור סוגים מספריים אינטגרליים אחרים, כגון קצר, int, או ארוך.

נניח שיש לנו איזה סוג בתים עם הערך של 100. למספר זה יש את הייצוג הבינארי 0110_0100.

בואו נכפיל את הערך הזה:

בתים b1 = 100; בתים b2 = (בתים) (b1 << 1);

אופרטור המשמרת השמאלי בקוד הנתון מעביר את כל הביטים במשתנה b1 עמדה משמאל, מה שמבחינה טכנית הופך את ערכה לגדול פי שניים. הייצוג הבינארי של המשתנה b2 יהיה אז 1100_1000.

במערכת מסוג לא חתום, ערך זה מייצג מספר עשרוני השווה ל- 2^7 + 2^6 + 2^3, או 200. על כל פנים, במערכת חתומה, הסיבית השמאלית ביותר עובדת כסימן הסימן. לכן, התוצאה היא -2^7 + 2^6 + 2^3, או -56.

בדיקה מהירה יכולה לאמת את התוצאה:

assertEquals (-56, b2);

אנו יכולים לראות שהחישובים של מספרים חתומים ולא חתומים זהים. הבדלים מופיעים רק כאשר ה- JVM מפרש ייצוג בינארי כמספר עשרוני.

פעולות החיבור, החיסור והכפל יכולות לעבוד עם מספרים שלא חתומים ללא צורך בשינויים ב- JDK. פעולות אחרות, כגון השוואה או חלוקה, מטפלות באופן שונה במספרים חתומים ולא חתומים.

כאן נכנס לתמונה ממשק ה- API הלא שלם שלם.

3. ממשק ה- API שלם ללא חתום

ה- API הלא שלם שלם ללא תמיכה מספק תמיכה בחשבון מספרים שלם שלא חתום ב- Java 8. רוב חברי ה- API הזה הם שיטות סטטיות ב מספר שלם ו ארוך שיעורים.

השיטות בשיעורים אלה פועלות באופן דומה. כך נתמקד ב מספר שלם בכיתה בלבד, ומשאיר את ארוך כיתה לקיצור.

3.1. השוואה

ה מספר שלם class מגדיר שיטה בשם CompareUnsigned להשוות מספרים שלא חתומים. שיטה זו מחשיבה את כל הערכים הבינאריים כלא חתומים, תוך התעלמות מרעיון הסימן.

נתחיל בשני מספרים בגבולות ה- int סוג מידע:

int חיובי = Integer.MAX_VALUE; int שלילי = Ingerger.MIN_VALUE;

אם נשווה את המספרים הללו כערכים חתומים, חִיוּבִי הוא כמובן גדול מ שלילי:

int חתוםComparison = Integer.compare (חיובי, שלילי); assertEquals (1, חתום השוואה);

כאשר משווים מספרים כערכים לא חתומים, הסיבית השמאלית ביותר נחשבת לסיבית המשמעותית ביותר במקום סיבית הסימן. לפיכך, התוצאה שונה, עם חִיוּבִי להיות קטן מ שלילי:

int unsignedComparison = Integer.compareUnsigned (חיובי, שלילי); assertEquals (-1, unsignedComparison);

זה צריך להיות ברור יותר אם נסתכל על הייצוג הבינארי של המספרים האלה:

  • ערך מקסימלי ->0111_1111_…_1111
  • MIN_VALUE ->1000_0000_…_0000

כאשר הסיבית השמאלית ביותר היא סיבית ערך רגיל, MIN_VALUE היא יחידה אחת הגדולה מ ערך מקסימלי במערכת הבינארית. בדיקה זו מאשרת כי:

assertEquals (שלילי, חיובי + 1);

3.2. חטיבה ומודולו

בדיוק כמו פעולת ההשוואה, פעולות החלוקה והמודולו לא חתומות מעבדות את כל הביטים כסיביות ערך. לכן המנות והשאריות שונות כאשר אנו מבצעים פעולות אלה במספרים חתומים ולא חתומים:

int חיובי = Integer.MAX_VALUE; int שלילי = שלם.MIN_VALUE; assertEquals (-1, שלילי / חיובי); assertEquals (1, Integer.divideUnsigned (שלילי, חיובי)); assertEquals (-1,% חיובי שלילי); assertEquals (1, Integer.remainderUnsigned (שלילי, חיובי));

3.3. ניתוח

בעת ניתוח א חוּט משתמש ב parseUnsignedInt שיטה, טיעון הטקסט יכול לייצג מספר גדול מ- ערך מקסימלי.

ערך גדול כזה לא ניתן לניתוח עם ה- מזויף שיטה, שיכולה להתמודד רק עם ייצוג טקסטואלי של מספרים מ- MIN_VALUE ל ערך מקסימלי.

מקרה הבדיקה הבא מאמת את תוצאות הניתוח:

נזרק לזרוק = catchThrowable (() -> Integer.parseInt ("2147483648")); assertThat (נזרק) .isInstanceOf (NumberFormatException.class); assertEquals (Integer.MAX_VALUE + 1, Integer.parseUnsignedInt ("2147483648"));

שימו לב שה- parseUnsignedInt השיטה יכולה לנתח מחרוזת המציינת מספר גדול מ- ערך מקסימלי, אך לא יצליח לנתח ייצוג שלילי כלשהו.

3.4. עיצוב

בדומה לניתוח, בעת עיצוב מספר, פעולה לא חתומה מתייחסת לכל הביטים לסיביות ערך. כתוצאה מכך, אנו יכולים לייצר ייצוג טקסטואלי של מספר גדול פי שניים מכפי ערך מקסימלי.

מקרה הבדיקה הבא מאשר את תוצאת העיצוב של MIN_VALUE בשני המקרים - חתום ולא חתום:

מחרוזת signString = Integer.toString (Integer.MIN_VALUE); assertEquals ("- 2147483648", חתום מחרוזת); String unsignedString = Integer.toUnsignedString (Integer.MIN_VALUE); assertEquals ("2147483648", unsignedString);

4. יתרונות וחסרונות

מפתחים רבים, במיוחד כאלה המגיעים משפה התומכת בסוגי נתונים לא חתומים, כגון C, מברכים על הצגת פעולות חשבון לא חתומות. למרות זאת, זה לא בהכרח דבר טוב.

ישנן שתי סיבות עיקריות לדרישה למספרים שלא חתומים.

ראשית, ישנם מקרים שערך שלילי לעולם לא יכול להתרחש, ושימוש בסוג לא חתום יכול למנוע ערך כזה מלכתחילה. שנית, עם סוג לא חתום, אנחנו יכולים להכפיל את טווח הערכים החיוביים הניתנים לשימוש לעומת מקבילו החתום.

בואו ננתח את הרציונל העומד מאחורי הערעור למספרים שלא חתומים.

כאשר משתנה צריך להיות תמיד לא שלילי, ערך קטן מ- 0 עשוי להיות שימושי לציון מצב יוצא דופן.

למשל, ה String.indexOf שיטה מחזירה את המיקום של המופע הראשון של תו מסוים במחרוזת. המדד -1 יכול לציין בקלות את היעדרו של תו כזה.

הסיבה הנוספת למספרים לא חתומים היא הרחבת שטח הערך. למרות זאת, אם הטווח של סוג חתום אינו מספיק, אין זה סביר שטווח כפול יספיק.

במקרה וסוג נתונים אינו גדול מספיק, עלינו להשתמש בסוג נתונים אחר התומך בערכים גדולים בהרבה, כגון שימוש ארוך במקום int, או ביג-שלם ולא ארוך.

בעיה נוספת עם ממשק ה- API שלם ללא חתום היא שהצורה הבינארית של מספר זהה ללא קשר אם היא חתומה או לא חתומה. לכן זה קל לערבב ערכים חתומים ולא חתומים, מה שעלול להוביל לתוצאות בלתי צפויות.

5. מסקנה

התמיכה בחשבון לא חתום בג'אווה הגיעה לבקשת אנשים רבים. עם זאת, היתרונות שהוא מביא אינם ברורים. עלינו לנקוט משנה זהירות בעת שימוש בתכונה חדשה זו כדי למנוע תוצאות בלתי צפויות.

כמו תמיד, קוד המקור של מאמר זה זמין באתר GitHub.