สตริงที่ถูกสั่งให้กลายเป็นตัวเลข
ลูกค้ากดยืนยันออเดอร์จากหน้าเว็บ แล้ว orderId กับ total เดินขึ้นมาถึง service ของคุณ
เป็นข้อความสองเส้น ไม่ใช่ตัวเลขสองค่า
ช่องในฟอร์มเป็นสตริง query parameter เป็นสตริง และ header ก็เป็นสตริง ฝั่งที่ส่งไม่มีชนิด จำนวนเต็มให้ใช้ตั้งแต่ต้น การแปลงจึงเป็นคำสั่งที่คุณต้องเขียนเองทุกครั้ง
คำถามของบทนี้ไม่ใช่ว่าภาษาไหน parse ได้ถูกกว่ากัน แต่เป็นว่า เมื่อสตริงนั้นไม่พอดีกับชนิด ปลายทาง คุณถืออะไรอยู่ในมือที่บรรทัดถัดไป คำตอบที่วัดได้มีสี่แบบ และสองแบบในนั้นคือตัวเลข ที่หน้าตาใช้งานได้
orderctl คือเครื่องมือประมวลผลออเดอร์ที่คอร์สภาษาในเสานี้สร้างซ้ำทีละภาษา repo ตัวอย่าง
กำลังจัดทำ ระหว่างนี้ทุกตัวเลขในบทนี้รันเองได้ด้วย npm run verify:integer-semantics
ซึ่งเรียก python3 scripts/integer-semantics/probe.py · ไม่ต้องมี Docker และไม่ต้องต่อเน็ต
สตริงสี่เส้น กับการเรียกสิบสองครั้ง
หัวข้อที่มีชื่อว่า “สตริงสี่เส้น กับการเรียกสิบสองครั้ง”สิบสองแถวนี้รับสตริงจากชุดสี่เส้นเดียวกัน และต่างกันที่ค่ากับ error ที่ออกมาจากการเรียกนั้น
สตริงที่ probe ป้อนเข้าไปมีสี่เส้น คือ 3000000000 ที่เกินช่วงของชนิด 32 bit ที่มีเครื่องหมาย
· 9007199254740993 ที่อยู่นอกช่วง safe integer ของ double · 12345678901234567890
ที่ยาวยี่สิบหลัก และ 12ab ที่ไม่ใช่ตัวเลขทั้งเส้น
| การเรียก | ค่าที่คืนมา | error ที่ออกมาพร้อมกัน |
|---|---|---|
Java Integer.parseInt("3000000000") | (ไม่มี) | NumberFormatException |
Java Long.parseLong("9007199254740993") | 9007199254740993 | (ไม่มี) |
Java Long.parseLong("12345678901234567890") | (ไม่มี) | NumberFormatException |
Go strconv.Atoi("12345678901234567890") | 9223372036854775807 | value out of range |
Go strconv.ParseInt("3000000000", 10, 32) | 2147483647 | value out of range |
Node parseInt("9007199254740993") | 9007199254740992 | (ไม่มี) |
Node Number("12345678901234567890") | 12345678901234567000 | (ไม่มี) |
Node BigInt("12345678901234567890") | 12345678901234567890 | (ไม่มี) |
Node parseInt("12ab") | 12 | (ไม่มี) |
Node Number("12ab") | NaN | (ไม่มี) |
Python int("12345678901234567890") | 12345678901234567890 | (ไม่มี) |
Python int("12ab") | (ไม่มี) | ValueError |
สิบสองแถวนี้แบ่งออกเป็นสามกองตามสิ่งที่เหลืออยู่ในมือผู้เรียก
- สามแถวไม่มีค่าคืนมาเลย คือสองแถวของ Java และแถวสุดท้ายของ Python
- สองแถวคืนทั้งค่าและ error พร้อมกัน และทั้งสองแถวเป็นของ Go
- เจ็ดแถวคืนค่ามาโดยไม่มี error สักตัว คือห้าแถวของ Node หนึ่งแถวของ Java และหนึ่งแถวของ Python
กองที่สามคือกองที่ใหญ่ที่สุด และสี่แถวในนั้นคืนค่าที่ไม่เท่ากับสตริงที่รับเข้าไป สามแถวในสี่แถว
นั้นคืนจำนวนเต็มที่หน้าตาใช้งานได้ ส่วนแถวที่เหลือคืน NaN ซึ่งประกาศตัวว่าผิด
สี่นโยบาย บนพรมแดนเดียวกัน
หัวข้อที่มีชื่อว่า “สี่นโยบาย บนพรมแดนเดียวกัน”ภาษาทั้งสี่รับงานเดียวกัน และต่างกันที่สิ่งที่มันทำเมื่อทำงานนั้นไม่สำเร็จ
พรมแดนของการ parse มีนโยบายอยู่สี่แบบ คือโยน · คืนค่าพร้อม error · ไม่ล้มเหลว · และตรงเป๊ะ หัวข้อย่อยสี่หัวข้อข้างล่างวัดทีละแบบจากทรงที่ probe รันจริง
นโยบายที่หนึ่ง · Java โยน แล้วไม่มีค่าให้ถือต่อ
หัวข้อที่มีชื่อว่า “นโยบายที่หนึ่ง · Java โยน แล้วไม่มีค่าให้ถือต่อ”ทรงของ Java ทั้งสามทรงเรียกผ่านตัวช่วยชื่อ thrown ซึ่งเก็บชื่อ class กับข้อความของสิ่งที่โยน
เมื่อมีการโยน และเก็บค่าที่ได้เมื่อไม่มี
thrown("parseInt3e9", () -> Integer.parseInt("3000000000"));thrown("parseLongSafe", () -> Long.parseLong("9007199254740993"));thrown("parseLongBig", () -> Long.parseLong("12345678901234567890"));ทรงแรกกับทรงที่สามโยน และ probe บันทึกสองบรรทัดนี้ไว้ โดยต่อชื่อ class เข้ากับข้อความของ
exception ด้วย ": " ซึ่งเป็นรูปเดียวกับบรรทัดหัวของ stack trace
java.lang.NumberFormatException: For input string: "3000000000"java.lang.NumberFormatException: For input string: "12345678901234567890"สองบรรทัดนี้ไม่มีค่าที่ขยับระหว่างรอบอยู่เลย ไม่มีหมายเลข thread ไม่มีที่อยู่ในหน่วยความจำ probe จึงผูกมันไว้ได้ทั้งบรรทัด และช่องค่าของทั้งสองทรงเป็นสตริงว่าง
ทรงที่สองไม่โยน มันคืน 9007199254740993 ครบทุกหลัก ซึ่งบอกว่าสิ่งที่ Java ปฏิเสธไม่ใช่
ความยาวของสตริง แต่เป็นการที่ค่านั้นไม่พอดีกับความกว้างของชนิดที่คุณเลือกไว้รับ
probe เรียก Integer.parseInt รูปอาร์กิวเมนต์เดียว ส่วนเงื่อนไขของการโยนเขียนไว้ที่รูปสอง
อาร์กิวเมนต์ ข้อต่อระหว่างสองรูปอยู่ในเอกสารของรูปอาร์กิวเมนต์เดียวเอง ที่เขียนว่าค่าที่ได้
is returned, exactly as if the argument and the radix 10 were given as arguments to the
parseInt(java.lang.String, int) method
เอกสารของ Integer.parseInt(String, int) จึงไล่เงื่อนไขที่ทำให้เกิด NumberFormatException
ไว้สี่ข้อให้ทั้งสองรูป สามข้อแรกเป็นเรื่องรูปร่างของสตริงกับฐานที่ใช้ ส่วนข้อสุดท้ายคือ The value
represented by the string is not a value of type int ซึ่งเป็นข้อที่ตรงกับทรงที่ probe วัด
เส้นทางที่ล้มเหลวของ Java ไม่ได้ผลิตค่าออกมาให้ถือเลยสักค่า คุณจึงไม่มีทางถือตัวเลขผิดๆ
ไว้โดยไม่รู้ตัวจากทรงเหล่านี้ ผู้เรียกที่ไม่ catch จะได้ exception ขึ้นไปถึงชั้นบน
ราคาที่ต้องจ่ายคือคุณต้องเขียนเส้นทางนั้นเองทุกที่ที่ parse การลืม catch หนึ่งที่แปลว่า
request ใบนั้นจบด้วย error ที่ชั้นบนสุด แทนที่จะจบด้วยข้อความบอกผู้ใช้ว่าช่องไหนกรอกผิด
นโยบายที่สอง · Go คืนค่ามาด้วย และค่านั้นคือขอบของชนิดเมื่อค่าเกินช่วง
หัวข้อที่มีชื่อว่า “นโยบายที่สอง · Go คืนค่ามาด้วย และค่านั้นคือขอบของชนิดเมื่อค่าเกินช่วง”Go คืนสองค่าจากการเรียกครั้งเดียว และ probe อ่านทั้งสองค่าทุกครั้ง ไม่ได้ทิ้งค่าที่สองทิ้ง
ai, aerr := strconv.Atoi("12345678901234567890")out["atoiBig"] = fmt.Sprintf("%d", ai)out["atoiBigErr"] = errText(aerr)pi, ierr := strconv.ParseInt("3000000000", 10, 32)out["parseInt32"] = fmt.Sprintf("%d", pi)out["parseInt32Err"] = errText(ierr)ช่อง error ของทั้งสองทรงไม่ว่าง และ probe เก็บข้อความของมันมาทั้งบรรทัด
strconv.Atoi: parsing "12345678901234567890": value out of rangestrconv.ParseInt: parsing "3000000000": value out of rangeส่วนช่องค่าของทั้งสองทรงก็ไม่ว่างเหมือนกัน atoiBig เป็น 9223372036854775807 และ
parseInt32 เป็น 2147483647 ซึ่งเป็นค่ามากที่สุดของชนิด 64 bit กับของชนิด 32 bit
ที่มีเครื่องหมาย ตามลำดับ
เอกสารของ strconv เขียนกฎนี้ไว้ในคำอธิบายของ ParseInt ตรงๆ
If s is empty or contains invalid digits, err.Err = ErrSyntax and the returned value is 0;if the value corresponding to s cannot be represented by a signed integer of the givensize, err.Err = ErrRange and the returned value is the maximum magnitude integer of theappropriate bitSize and sign.ประโยคนี้มีสองครึ่ง และสองทรงที่บทนี้วัดอยู่ในครึ่งหลังทั้งคู่ คือครึ่งของ ErrRange ส่วน
ครึ่งของ ErrSyntax ที่คืน 0 ไม่มีทรงไหนในบทนี้วัดไว้ ข้ออ้างทุกข้อข้างล่างจึงเป็นข้ออ้าง
ของครึ่งที่ค่าเกินช่วง ไม่ใช่ของทุกการเรียกที่ล้มเหลว
หน้าเดียวกันยังเขียนว่า Atoi is equivalent to ParseInt(s, 10, 0), converted to type int
ดังนั้น bitSize ของ Atoi คือ 0 ซึ่งหมายถึงชนิด int ความกว้างของ int ขึ้นกับ
เครื่อง และเครื่องที่ probe รันคือ darwin/arm64 ซึ่งให้ 9223372036854775807
สำนวนที่เห็นบ่อยที่สุดคือการทิ้งค่าที่สองทิ้งด้วย _ เพราะมันสั้นกว่าและ compiler ไม่บ่น
n, _ := strconv.Atoi(orderIdFromQuery)ค่าตัวแรกที่ probe อ่านได้จากการเรียกแบบเดียวกันคือ 9223372036854775807 การทิ้ง error
ทิ้งจึงไม่ได้ทำให้ n เป็น 0 มันเพียงลบสิ่งเดียวที่บอกว่า n ไม่ใช่ค่าที่ส่งมาออกไป
นี่คือจุดที่คมที่สุดของบทนี้ ผู้เรียกที่คุ้นกับภาษาที่โยน จะอ่านบรรทัดนี้ว่า “ถ้าแปลงไม่ได้ก็ได้
ศูนย์” แล้วเขียนเงื่อนไข if n == 0 ไว้ดัก ซึ่งเป็นเงื่อนไขที่ไม่มีวันเป็นจริงในทรงนี้
นโยบายที่สาม · Node ไม่ล้มเหลวสักทรงในห้าทรงที่วัด
หัวข้อที่มีชื่อว่า “นโยบายที่สาม · Node ไม่ล้มเหลวสักทรงในห้าทรงที่วัด”ทรงของ Node มีห้าทรง และไม่มีทรงไหนโยนอะไรออกมาเลยสักทรง ข้ออ้างนี้เป็นของห้าทรงนี้ ไม่ใช่ของทุกคำสั่งในภาษา
out.parseIntSafe = S(parseInt("9007199254740993"));out.numberBig = S(Number("12345678901234567890"));out.bigintBig = BigInt("12345678901234567890").toString();out.parseIntTrailing = S(parseInt("12ab"));out.numberTrailing = S(Number("12ab"));สองทรงสุดท้ายรับสตริงเส้นเดียวกัน คือ 12ab แล้วตอบคนละอย่าง parseInt ให้ 12 ส่วน
Number ให้ NaN นี่คืออินพุตเดียวกัน สองคำตอบ ในภาษาเดียว
MDN อธิบายฝั่ง parseInt ไว้ว่า เมื่อมันเจออักขระที่ไม่ใช่ตัวเลขของฐานที่ใช้อยู่ มันจะข้าม
อักขระนั้นและทุกอักขระที่ตามมา แล้วคืนค่าจำนวนเต็มเท่าที่ parse มาได้ถึงตรงนั้น
ส่วนฝั่ง Number MDN อธิบายขั้นตอนของการแปลงเป็นตัวเลขไว้ว่า สตริงถูกแปลงด้วยการ parse
มันเสมือนเป็น number literal และ Parsing failure results in NaN
ทรงแรกไม่มีอักขระแปลกปลอมเลย 9007199254740993 เป็นตัวเลขฐานสิบทุกหลัก แต่ผลที่วัดได้
คือ 9007199254740992 ซึ่งหายไปหนึ่ง เพราะค่าที่ parseInt คืนเป็น number ของ JavaScript
MDN เขียนข้อจำกัดนี้ไว้ในหน้าเดียวกัน ว่า parseInt() คืน number จึงเสียความแม่นยำได้ถ้า
จำนวนเต็มที่สตริงนั้นแทนอยู่นอกช่วง safe integer แล้วชี้ไปที่ BigInt() ซึ่ง parse จำนวนเต็ม
ความยาวเท่าไรก็ได้อย่างตรงตัว
ทรงที่สามคือทางออกที่หน้านั้นชี้ไป BigInt("12345678901234567890") คืน
12345678901234567890 ครบยี่สิบหลัก ขณะที่ Number กับสตริงเส้นเดียวกันคืน
12345678901234567000 ซึ่งลงท้ายด้วยศูนย์สามตัวที่ไม่ได้ส่งมา
นโยบายที่สี่ · Python ตรงทุกหลัก หรือไม่ก็หยุด
หัวข้อที่มีชื่อว่า “นโยบายที่สี่ · Python ตรงทุกหลัก หรือไม่ก็หยุด”int ของ Python ไม่มีความกว้าง จึงไม่มีเงื่อนไข “ค่าเกินช่วง” ให้ล้มเหลวตั้งแต่แรก สิ่งที่
int() ปฏิเสธจึงไม่ใช่ขนาดของค่า แต่เป็นรูปร่างของสตริง และความยาวของสตริง
out["intBig"] = str(int("12345678901234567890"))try: out["intTrailing"] = str(int("12ab")) out["intTrailingErr"] = ""except ValueError as exc: out["intTrailing"] = "" out["intTrailingErr"] = f"{type(exc).__name__}: {exc}"ทรงแรกคืน 12345678901234567890 ครบทุกหลัก ส่วนทรงที่สองเข้าไปในกิ่ง except และ probe
บันทึกชื่อ exception ต่อด้วยข้อความของมันไว้บรรทัดนี้
ValueError: invalid literal for int() with base 10: '12ab'เอกสารของ int ไล่สิ่งที่สตริงมีได้ไว้ครบ คือเครื่องหมาย + หรือ - นำหน้าโดยไม่เว้นวรรค
· ศูนย์นำหน้า · ช่องว่างล้อมรอบ · และขีดล่างเดี่ยวๆ คั่นระหว่างตัวเลข สตริง 12ab ไม่ตรงกับ
ข้อไหนเลย เพราะ ab ไม่ใช่ตัวเลขของฐานสิบ
รูปร่างไม่ใช่สิ่งเดียวที่ int() ปฏิเสธได้ หน้าเดียวกันมีหมายเหตุของ Python 3.11 ที่บอกว่า
สตริงที่ป้อนเข้า int และข้อความที่ int เขียนออกมา ถูกจำกัดความยาวได้เพื่อกันการโจมตีแบบ
ปฏิเสธการให้บริการ แล้วเขียนต่อว่า A ValueError is raised when the limit is exceeded while
converting a string to an int
สตริงที่เป็นตัวเลขฐานสิบทุกหลักจึงยังล้มเหลวได้ ถ้ามันยาวเกินขีดจำกัดนั้น ขนาดของขีดจำกัดไม่ได้
อยู่ใน measured.json เพราะ probe ของคอร์สนี้ไม่ได้วัดมัน บทนี้จึงไม่พิมพ์ตัวเลขของมันออกมา
สิ่งที่หน้าที่อ้างยืนยันได้คือ int() มีทางล้มเหลวมากกว่าหนึ่งทาง และทั้งสองทางยก ValueError
flowchart TD
S["สตริง 12345678901234567890 จากภายนอก"] --> J["Java · Long.parseLong"]
S --> G["Go · strconv.Atoi"]
S --> N["Node · Number"]
S --> P["Python · int"]
J --> J1["โยน NumberFormatException · ไม่มีค่าให้ถือ"]
G --> G1["คืน 9223372036854775807 พร้อม error"]
N --> N1["คืน 12345678901234567000 ไม่มี error"]
P --> P1["คืน 12345678901234567890 ครบทุกหลัก"]
G1 --> D{"ผู้เรียกอ่านค่าที่สองหรือไม่"}
D -->|"อ่าน err"| DX["หยุดที่พรมแดน · รูปเดียวกับฝั่ง Java คือไม่มีตัวเลขให้ถือต่อ"]
D -->|"ทิ้งด้วย _"| DY["ถือค่าที่ขอบต่อไป · รูปเดียวกับฝั่ง Node คือถือตัวเลขคนละค่ากับอินพุต"]
คำบรรยายภาพ: สี่กิ่งบนรับสตริงเส้นเดียวกัน สองกิ่งล่างเทียบที่รูปของผลลัพธ์ ไม่ใช่ที่ตัวเลข ซึ่งไม่เท่ากันสักคู่ · และกิ่งของ Go เป็นกิ่งเดียวที่ยังแตกต่อได้อีก เพราะนโยบายของมันมอบการตัดสินใจกลับมาที่ผู้เรียก ส่วนอีกสามกิ่งตัดสินใจแทนไปแล้วตั้งแต่ในภาษา
ความเสียหาย · order id ที่ไม่มีใครบอกว่าเพี้ยน
หัวข้อที่มีชื่อว่า “ความเสียหาย · order id ที่ไม่มีใครบอกว่าเพี้ยน”query parameter ใบเดียวเดินเข้าไปหาสี่ service แล้วออกมาเป็นผลสี่แบบที่ไม่ตรงกันเลย
orderId ที่ยาวยี่สิบหลักเดินเข้ามาทาง query parameter ใบเดียว ปลายทางทั้งสี่รับสตริงเส้น
เดียวกัน แล้วได้ผลสี่แบบตามตารางข้างบน
- service ที่เขียนด้วย Java ผ่าน
Long.parseLongจบ request ด้วย exception และไม่มีแถวไหนถูกแตะ - service ที่เขียนด้วย Go แบบทิ้ง error ทิ้ง ถือ
9223372036854775807ไปค้นต่อ - service ที่เขียนด้วย Node ผ่าน
Numberถือ12345678901234567000ไปค้นต่อ - service ที่เขียนด้วย Python ถือ
12345678901234567890ซึ่งตรงกับที่ส่งมา
สองเส้นกลางคือเส้นที่เจ็บ ค่าที่มันถือเป็นจำนวนเต็มบวกที่มีหน้าตาเหมือน order id ทุกประการ มันจึงเดินต่อเข้าไปในคำสั่งค้น เข้าไปใน log และเข้าไปใน metric ได้โดยไม่มีใครทักท้วง
9223372036854775807 ไม่ได้เป็นค่าสุ่ม มันเป็นค่าเดิมทุกครั้งที่สตริงของจำนวนบวกเกินช่วง
ออเดอร์คนละใบที่ id ยาวเกินจึงตกลงไปอยู่ที่ค่าเดียวกันหมด
เอกสารเขียนว่าค่าที่คืนคือจำนวนเต็มขนาดมากที่สุด of the appropriate bitSize and sign เครื่องหมายจึงเป็นส่วนหนึ่งของกฎ สตริงของจำนวนลบที่เกินช่วงได้ค่าที่ขอบล่างของชนิดแทน ไม่ใช่ค่าเดียวกันนี้ · ทรงที่ probe วัดไว้ทั้งสองทรงเป็นจำนวนบวก บทนี้จึงพิมพ์ค่าของฝั่งบวกอย่างเดียว
ถ้าค่านั้นบังเอิญตรงกับแถวที่มีอยู่จริงใน database ทุกออเดอร์ที่เพี้ยนจะชี้ไปที่แถวเดียวกัน และถ้าไม่ตรง คำสั่งค้นจะคืนศูนย์แถว ซึ่งอ่านเหมือน “ไม่พบออเดอร์” ไม่ใช่ “แปลงค่าไม่สำเร็จ”
ช่องยอดรวมเจ็บคนละแบบ สมมติผู้ใช้วางข้อความที่มีอักขระติดมาด้วยจนกลายเป็น 12ab ฝั่ง Node
ที่ใช้ parseInt จะได้ 12 แล้วบันทึกยอดนั้นลงไปเหมือนยอดปกติ
ฝั่ง Python ที่ใช้ int จะยก ValueError ที่บรรทัดนั้นเลย ส่วนฝั่ง Node ที่ใช้ Number
จะได้ NaN ซึ่งไม่ได้หยุดโปรแกรม แต่ทำให้การคำนวณทุกขั้นที่ตามมาได้ NaN ต่อกันไป
ความต่างของสามเส้นทางนี้ไม่ได้อยู่ที่ว่าภาษาไหนเข้มงวดกว่ากัน แต่อยู่ที่ว่าความผิดพลาดถูก รายงานที่บรรทัดที่เกิด หรือถูกเลื่อนไปโผล่ที่ไหนสักแห่งข้างหน้าในรูปของตัวเลขที่ดูปกติ
จุดที่ห้าในห้าจุด · ครึ่งที่นโยบายตอนล้มเหลวตัดสิน
หัวข้อที่มีชื่อว่า “จุดที่ห้าในห้าจุด · ครึ่งที่นโยบายตอนล้มเหลวตัดสิน”บทที่ 6 กับบทนี้อยู่ที่จุดเดียวกัน แต่วัดคนละครึ่งของมัน และครึ่งนี้มีทั้งเส้นที่หยุดทันที กับเส้นที่เงียบ
ธีสิสของคอร์สนี้คือ ค่าจำนวนเต็มค่าหนึ่งเปลี่ยนได้ห้าจุดระหว่างทางผ่านระบบ และทุกจุดในห้าจุดนั้น เงียบ จุดที่ห้าคือพรมแดนของการ parse ซึ่งบทที่ 6 เปิดไว้ด้วยตัวเลขที่อยู่ใน JSON
ครึ่งที่บทที่ 6 วัด คือครึ่งที่ชนิดปลายทางตัดสิน อินพุตเป็นตัวเลขอยู่แล้ว และสิ่งที่คุณเขียนมี อย่างเดียวคือชนิดของตัวแปรที่รับค่า
ครึ่งที่บทนี้วัดเริ่มจากอินพุตที่ไม่ใช่ตัวเลขตั้งแต่ต้น คุณจึงต้องเรียกคำสั่งแปลงด้วยตัวเอง และ สิ่งที่ตัดสินไม่ใช่ชนิดปลายทางอย่างเดียวแล้ว แต่เป็นนโยบายของคำสั่งนั้นเมื่อแปลงไม่สำเร็จ
สี่นโยบายที่บทนี้วัด สรุปได้เป็นสี่บรรทัดตามสิ่งที่เหลืออยู่ในมือผู้เรียก
- Java · โยน — ไม่มีค่าออกมาให้ถือเลยสักค่า
- Go · คืนค่าพร้อม error — ค่าที่คืนเมื่อค่าเกินช่วงคือขอบของชนิด และ error คือสิ่งเดียวที่บอกว่ามันคือขอบ
- Node · ไม่ล้มเหลวสักทรงในห้าทรงที่วัด — คืนค่าที่ปัดแล้ว ค่าที่ตัดท้ายทิ้งแล้ว หรือ
NaNแล้วแต่คำสั่งที่เรียก - Python · ตรงเป๊ะ หรือยก
ValueError— ไม่มีความกว้างให้ล้น สิ่งที่ปฏิเสธได้คือรูปร่างของสตริง กับความยาวของมัน
สองในสี่บรรทัดนี้ส่งจำนวนเต็มที่หน้าตาใช้งานได้กลับมาเมื่อแปลงไม่สำเร็จ คือบรรทัดของ Go กับ บรรทัดของ Node และทั้งสองค่าเดินต่อเข้าไปในระบบได้โดยไม่ต้องผ่านด่านไหนอีก
สิ่งที่ตัดสินว่าคุณได้บรรทัดไหนจึงไม่ใช่ชื่อภาษา แต่เป็นนโยบายของคำสั่งที่คุณเรียก และภาษาเดียว
มีคำสั่งที่ให้ผลคนละแบบได้ อย่างที่ parseInt กับ Number ของ Node ตอบ 12ab คนละอย่าง
ทางออกจึงอยู่ที่การเลือกคำสั่ง ไม่ใช่การเลือกภาษา Node มี BigInt ที่คืนยี่สิบหลักครบ ส่วน Go
มี err วางอยู่ตรงนั้นแล้ว รอแค่ให้ผู้เรียกอ่านมันแทนที่จะเขียน _ ทับ
บทที่ 8 เอาทั้งห้าจุดมาต่อกันเป็นท่อเดียว แล้ววัดยอดที่ออกปลายทางของท่อสองเส้นบนอินพุตชุด เดียวกัน ไม่ใช่บรรยายว่าเส้นไหนดีกว่า
-
Integer (Java SE 25 & JDK 25) (เข้าถึง 2026-09-13) —
parseInt(String)ระบุว่าโยนNumberFormatExceptionเมื่อสตริง ไม่มีจำนวนเต็มที่ parse ได้อยู่ · ส่วนparseInt(String, int)ไล่เงื่อนไขของการโยนไว้สี่ข้อ และข้อสุดท้ายคือ The value represented by the string is not a value of type int ซึ่งเป็น ข้อที่แยกการล้นของช่วง ออกจากการที่สตริงมีอักขระที่ไม่ใช่ตัวเลขของฐานนั้น -
Long (Java SE 25 & JDK 25) (เข้าถึง 2026-09-13) —
parseLong(String)ระบุกฎชุดเดียวกันกับปลายทางเป็นlongและ ระบุเพิ่มว่าอักษรLหรือlท้ายสตริงไม่ได้รับอนุญาต ต่างจากที่เขียนได้ใน source -
Go package documentation —
strconv(เข้าถึง 2026-09-13) —Atoiระบุว่าเทียบเท่ากับParseInt(s, 10, 0)ที่แปลงเป็นชนิดint·ParseIntระบุว่าเมื่อค่าที่สตริงแทนอยู่ใส่ลงจำนวนเต็มมีเครื่องหมายขนาดที่ขอไม่ได้err.Errจะเป็นErrRangeและค่าที่คืนคือจำนวนเต็มที่มีขนาดมากที่สุดของbitSizeกับ เครื่องหมายนั้น · ส่วนตัวแปรErrRangeเองนิยามไว้เป็นerrors.New("value out of range") -
parseInt()— MDN (เข้าถึง 2026-09-13) — ระบุว่าเมื่อเจออักขระที่ไม่ใช่ตัวเลขของฐานที่ใช้ มันข้ามอักขระนั้น และทุกอักขระที่ตามมา แล้วคืนค่าที่ parse มาได้ถึงตรงนั้น · และระบุว่าเพราะมันคืน number มันจึงเสียความแม่นยำได้เมื่อจำนวนเต็มนั้นอยู่นอกช่วง safe integer โดยชี้ไปที่BigInt() -
Number— MDN (เข้าถึง 2026-09-13) — หัวข้อ Number coercion ระบุว่าสตริงถูกแปลงด้วยการ parse มันเสมือน เป็น number literal และ Parsing failure results in NaN · หัวข้อเดียวกันระบุว่าNumber(x)ใช้ขั้นตอนชุดเดียวกันนี้ ต่างกันที่BigIntไม่โยนTypeError -
Built-in Functions — Python documentation (เข้าถึง 2026-09-15) —
class int(string, /, base=10)ระบุว่าสตริงมีเครื่องหมายนำหน้า โดยไม่เว้นวรรค ศูนย์นำหน้า ช่องว่างล้อมรอบ และขีดล่างเดี่ยวๆ คั่นระหว่างตัวเลขได้ · และระบุว่า ค่าฐานสิบ0ถึง9แทนด้วยอักขระตัวเลขของ Unicode ตัวใดก็ได้ · หมายเหตุ Changed in version 3.11 ของหน้าเดียวกันระบุว่าสตริงที่ป้อนเข้าintและข้อความที่intเขียนออกมา ถูกจำกัดความยาวได้ และ A ValueError is raised when the limit is exceeded while converting a string to an int · หน้านี้ชี้ต่อไปที่เอกสารของขีดจำกัดนั้นแทนการระบุขนาดของมันเอง -
ตัวเลขทุกตัวในบทนี้มาจาก
scripts/integer-semantics/measured.jsonซึ่งscripts/integer-semantics/probe.pyเขียนขึ้นหลังยิงทั้งชุดสามรอบต่อการเรียกหนึ่งครั้ง · วัดบนopenjdk 25.0.4.1go1.27.0node v24.19.0และPython 3.14.6บน darwin/arm64 เมื่อ 2026-09-12
เช็กความเข้าใจ — บทที่ 7
ข้อ 1 / 3เขียน `n, _ := strconv.Atoi("12345678901234567890")` ใน Go แล้ว `n` ถือค่าอะไรอยู่