วิธีการแปลงเลขทศนิยมให้เป็นจำนวนเต็ม?


47

นี่เป็นวิธีที่ถูกต้องในการแปลงทศนิยมให้เป็นจำนวนเต็มในการทุบตี? มีวิธีอื่นอีกไหม?

flotToint() {
    printf "%.0f\n" "$@"
}

5
"ถูกต้อง" คืออะไร?
โจเซฟอาร์

ฉันแค่อยากแน่ใจว่า .. ถูกหรือดีกว่านี้ไหม
ราหุลปาติล

2
%.0fจะปัดขึ้นหรือลง นั่นคือสิ่งที่คุณต้องการ? คุณสามารถใช้printf "%d\n" "$@" 2>/dev/nullเพื่อสับเศษส่วน
ott--

คำตอบ:


69

ทุบตี

ในbashนั้นอาจจะดีเท่าที่ได้รับ ที่ใช้ตัวเชลล์ในตัว หากคุณต้องการผลลัพธ์ในตัวแปรคุณสามารถใช้การทดแทนคำสั่งหรือbashเฉพาะ (ตอนนี้ยังสนับสนุนโดยzsh):

printf -v int %.0f "$float"

คุณสามารถทำได้:

float=1.23
int=${float%.*}

แต่นั่นจะลบส่วนที่เป็นเศษส่วนแทนที่จะให้จำนวนเต็มที่ใกล้ที่สุดกับคุณและนั่นจะไม่ทำงานสำหรับค่าที่$floatชอบ1.2e9หรือ.12ตัวอย่างเช่น

โปรดสังเกตข้อ จำกัด ที่เป็นไปได้เนื่องจากการเป็นตัวแทนภายในของการลอยตัว:

$ printf '%.0f\n' 1e50
100000000000000007629769841091887003294964970946560

คุณได้รับจำนวนเต็ม แต่มีโอกาสที่คุณจะไม่สามารถใช้จำนวนเต็มนั้นได้ทุกที่

นอกจากนี้ตามที่ระบุไว้โดย @BinaryZebra ในprintfการใช้งานหลายอย่าง(bash, ksh93, yash, ไม่ใช่ GNU, zsh, dash) จะได้รับผลกระทบจาก locale (ตัวคั่นทศนิยมซึ่งอาจเป็น.หรือ,)

ดังนั้นหากการลอยของคุณแสดงด้วยเครื่องหมายจุดเป็นทศนิยมเสมอและคุณต้องการให้มันเป็นเช่นนั้นโดยprintfไม่คำนึงถึงตำแหน่งที่ตั้งของผู้ใช้ที่เรียกใช้สคริปต์ของคุณคุณจะต้องแก้ไขโลแคลเป็น C:

LC_ALL=C printf '%.0f' "$float"

ด้วยyash, คุณสามารถทำได้:

printf '%.0f' "$(($float))"

(ดูด้านล่าง)

POSIX

printf "%.0f\n" 1.1

ไม่ใช่ POSIX เนื่องจาก%fไม่จำเป็นต้องได้รับการสนับสนุนจาก POSIX

POSIXly คุณสามารถทำ:

f2i() {
  awk 'BEGIN{for (i=1; i<ARGC;i++)
   printf "%.0f\n", ARGV[i]}' "$@"
}

อันนั้นไม่ได้รับผลกระทบจาก locale (เครื่องหมายจุลภาคไม่สามารถเป็นตัวคั่นทศนิยมได้awkเนื่องจากเป็นอักขระพิเศษในไวยากรณ์อยู่แล้ว ( print 1,2เช่นเดียวกับprint 1, 2การส่งผ่านสองอาร์กิวเมนต์ไปprint)

zsh

ในzsh(ซึ่งรองรับการคำนวณจุดลอยตัว (ตัวคั่นทศนิยมเป็นช่วงเวลาเสมอ)) คุณมีrint()ฟังก์ชันทางคณิตศาสตร์เพื่อให้จำนวนเต็มที่ใกล้ที่สุดเป็นทศนิยม(เช่นในC) และint()เพื่อให้คุณได้จำนวนเต็มจากการลอย (เช่นในawk) ดังนั้นคุณสามารถทำได้:

$ zmodload zsh/mathfunc
$ i=$((int(rint(1.234e2))))
$ echo $i
123

หรือ:

$ integer i=$((rint(5.678e2)))
$ echo $i
568

อย่างไรก็ตามโปรดทราบว่าในขณะที่doubles สามารถเป็นตัวแทนของตัวเลขที่มีขนาดใหญ่มากจำนวนเต็มมี จำกัด มากขึ้น

$ printf '%.0f\n' 1e123
999999999999999977709969731404129670057984297594921577392083322662491290889839886077866558841507631684757522070951350501376
$ echo $((int(1e123)))
-9223372036854775808

ksh93

ksh93 เป็นเชลล์แบบบอร์นตัวแรกที่รองรับการคำนวณเลขทศนิยม ksh93 ปรับการทดแทนคำสั่งให้เหมาะสมที่สุดโดยไม่ใช้ไพพ์หรือฟอร์กเมื่อคำสั่งเป็นคำสั่งในตัวเท่านั้น ดังนั้น

i=$(printf '%.0f' "$f")

ไม่แยก หรือดีกว่า:

i=${ printf '%.0f' "$f"; }

ซึ่งไม่ได้แยกอย่างใดอย่างหนึ่ง แต่ยังไม่ได้ไปปัญหาทั้งหมดของการสร้างสภาพแวดล้อม subshell ปลอม

คุณยังสามารถทำสิ่งต่อไปนี้

i=$((rint(f)))

แต่ระวัง:

$ echo "$((rint(1e18)))"
1000000000000000000
$ echo "$((rint(1e19)))"
1e+19

คุณสามารถทำได้:

integer i=$((rint(f)))

แต่ชอบzsh:

$ integer i=1e18
$ echo "$i"
1000000000000000000
$ integer i=1e19
$ echo "$i"
-9223372036854775808

ระวังว่าksh93เลขทศนิยมนั้นอิงตามการตั้งค่าตัวคั่นทศนิยมในโลแคล (แม้ว่า,จะเป็นตัวดำเนินการทางคณิตศาสตร์ ( $((1,2))จะเป็น 6/5 ในภาษาฝรั่งเศส / เยอรมัน ... โลแคลและเช่นเดียวกับ$((1, 2))นั่นคือ 2 ในโลแคลภาษาอังกฤษ) .

Yash

Yash ยังสนับสนุนลอยคำนวณจุด แต่ไม่ได้มีฟังก์ชั่นทางคณิตศาสตร์เช่นksh93/ 'szsh rint()คุณสามารถแปลงตัวเลขเป็นจำนวนเต็มได้โดยใช้ไบนารี่หรือโอเปอเรเตอร์เป็นต้น (สามารถใช้งานzshได้ แต่ไม่ใช่ksh93) โปรดทราบว่ามันตัดส่วนทศนิยมมันไม่ได้ให้จำนวนเต็มที่ใกล้ที่สุด:

$ echo "$((0.237e2 | 0))"
23
$ echo "$((1e19))"
-9223372036854775808

yash ให้เกียรติตัวคั่นทศนิยมของโลแคลบนเอาต์พุต แต่ไม่ใช่สำหรับค่าคงที่ตัวอักษรพอยท์ในการแสดงออกทางคณิตศาสตร์ซึ่งสามารถทำให้ประหลาดใจ:

$ LC_ALL=fr_FR.UTF-8 ./yash -c 'a=$((1e-2)); echo $(($a + 1))'
./yash: arithmetic: `,' is not a valid number or operator

เป็นวิธีที่ดีที่คุณสามารถใช้ค่าคงที่จุดลอยตัวในสคริปต์ของคุณที่ใช้ช่วงเวลาและไม่ต้องกังวลว่ามันจะหยุดทำงานในสถานที่อื่น แต่ก็ยังสามารถจัดการกับตัวเลขตามที่ผู้ใช้แสดงได้นาน ตามที่คุณจำได้ว่าต้องทำ:

var=$((10.3)) # and not var=10.3
... "$((a + 0.1))" # and not "$(($a + 0.1))".

printf '%.0f\n' "$((10.3))" # and not printf '%.0f\n' 10.3

int=${float%.*}ทำงานได้ดีมากในสคริปต์ทุบตีของฉัน (เวอร์ชัน 3.2.57 (1) - ปล่อย) บน Mac (รุ่น 10.13.4 (17E199))
TelamonAegisthus

การกำหนดครั้งแรกของคุณล้มเหลวใน GNU bash 4.4.19 เช่น: `` `$ echo $ สูงสุด 32.800 $ printf -v int% .0f" $ สูงสุด "bash: printf: 32.800: จำนวนที่ไม่ถูกต้อง` `
Luís de Sousa

@ LuísdeSousaสถานที่ของคุณอาจ,เป็นเลขฐานสิบ ดูในส่วนเกี่ยวกับLC_ALL=C
Stéphane Chazelas

โปรดทราบว่าคำตอบนี้ตอบคำถามที่แตกต่างกัน: วิธีการลบตัวเลขหลังจุดไม่ใช่สิ่งที่ถูกถาม: สิ่งที่ทางขวาวิธีการทำลอยจำนวนเต็ม
ไอแซค

ควรใช้วิธีนี้กับการลอยของบิตจำนวนเท่าใด? มันควรจะเหมือนกันสำหรับลอย 23 บิตหรือ 128 บิตแมนทิสซา?
ไอแซค

21

bc - ภาษาเครื่องคำนวณความแม่นยำตามอำเภอใจ

int (ลอย) ควรมีลักษณะดังนี้:

$ echo "$float/1" | bc 
1234

เพื่อปัดเศษให้ดีขึ้นใช้สิ่งนี้:

$ echo "($float+0.5)/1" | bc 

ตัวอย่าง:

$ float=1.49
$ echo "($float+0.5)/1" | bc 
1
$ float=1.50
$ echo "($float+0.5)/1" | bc 
2

4
แต่จะช่วยให้float=-2; echo "($float+0.5)/1" | bc -1
Stéphane Chazelas

2
นั่นเรียกว่าอ้อมไปยัง + inf ที่เป็นหนึ่งในสี่วิธีที่เป็นไปได้รอบ

5

คำตอบก่อนหน้านี้ที่ส่งเกือบถูกต้อง: "คุณสามารถทำได้:

float=1.23
int=${float%.*}

แต่นั่นจะลบส่วนที่เป็นเศษส่วนแทนที่จะให้จำนวนเต็มที่ใกล้ที่สุดและนั่นจะไม่สามารถใช้ได้กับค่าของ $ float เช่น 1.2e9 หรือ. 12 เป็นต้น .... "

${float%%.*}ใช้เพียงแค่

echo ${float%%.*}
1

3

แฮ็คที่ง่ายมาก ๆ ก็คือ

function float_to_int() { 
  echo $1 | cut -d. -f1    # or use -d, if decimals separator is ,
}

ตัวอย่างผลลัพธ์

$ float_to_int 32.333
32
$ float_to_int 32
32

มันเป็นไปได้ที่จะใช้sedแต่cutการแก้ปัญหาง่ายขึ้น ฉันชอบsedหรือcutเนื่องจากเป็น IMHO ที่มีอยู่ในเป้าหมายแบบฝังมากกว่าawk(ซึ่งยังคงพบได้บ่อยbusyboxกว่าbc)
pevik

0

ที่เกี่ยวข้อง:

  1. ทำ float เป็นจำนวนเต็มได้อย่างไร
  2. วิธีการปัดเลขทศนิยมในเปลือก?

การตอบสนอง@ Stéphane Chazelas awkตอบ:

float_to_integer_rounding_nearst() {
    awk 'BEGIN{for (i=1; i<ARGC;i++) printf "%.0f", ARGV[i]}' "$@";
}

float_to_integer_rounding_floor() {
    awk 'BEGIN{for (i=1; i<ARGC;i++) printf "%d", int( ARGV[i] )}' "$@";
}

ใน awks ของฉันหนึ่งรอบหลังไปที่ศูนย์ไม่ลบอนันต์ (เป็น "พื้น" สามารถนำไปหมายถึง) นอกจากนี้รอบแรกจะแบ่งครึ่งเป็นเลขคู่ ผมไม่แน่ใจว่าที่ใด ๆ ที่แตกต่างจากสิ่งทุบตีของprintfไม่หรือถ้ามีเหตุผลอื่นใดที่จะชอบมากกว่า awk printfbuiltin
ilkkachu
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.