Làm thế nào để chuyển đổi số dấu phẩy động thành số nguyên?


47

Đây có phải là cách đúng để thực hiện chuyển đổi số nguyên trong bash không? Có phương pháp nào khác không?

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

5
"Đúng" là gì?
Joseph R.

Tôi chỉ muốn chắc chắn .. điều này đúng hay còn hơn thế này?
Rahul Patil

2
%.0fsẽ làm tròn lên hoặc xuống. Đó là điều bạn muốn? Bạn có thể sử dụng printf "%d\n" "$@" 2>/dev/nullđể cắt các phần.
ott--

Câu trả lời:


70

bash

Trong bashđó, có lẽ là tốt như nó được. Điều đó sử dụng một vỏ dựng sẵn. Nếu bạn cần kết quả trong một biến, bạn có thể sử dụng thay thế lệnh hoặc bashcụ thể (mặc dù hiện tại cũng được hỗ trợ bởi zsh):

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

Bạn có thể làm:

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

Nhưng điều đó sẽ loại bỏ phần phân số thay vì cung cấp cho bạn số nguyên gần nhất và điều đó sẽ không hoạt động đối với các giá trị $floattương tự 1.2e9hoặc .12chẳng hạn.

Cũng lưu ý các hạn chế có thể do biểu diễn bên trong của phao:

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

Bạn nhận được một số nguyên, nhưng rất có thể bạn sẽ không thể sử dụng số nguyên đó ở bất cứ đâu.

Ngoài ra, như được lưu ý bởi @BinaryZebra, trong một số printftriển khai (bash, ksh93, yash, không phải GNU, zsh, dash), nó bị ảnh hưởng bởi miền địa phương (dấu tách thập phân có thể .hoặc ,).

Vì vậy, nếu số float của bạn luôn được biểu thị bằng dấu phân cách thập phân và bạn muốn nó được xử lý như vậy bằng cách printfbất kể ngôn ngữ của người dùng gọi tập lệnh của bạn, bạn cần sửa ngôn ngữ thành C:

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

Với yash, bạn cũng có thể làm:

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

(xem bên dưới).

MỘT BỘ MÔ TẢ CHÍNH THỨC CUNG CẤP MỘT TIÊU CHUẨN CHO THIẾT KẾ HỆ ĐIỀU HÀNH, ĐẶC BIỆT LÀ CÁC MÔ TẢ TƯƠNG THÍCH VỚI UNIX

printf "%.0f\n" 1.1

không phải là POSIX vì %fkhông bắt buộc phải được POSIX hỗ trợ.

POSIXly, bạn có thể làm:

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

Cái đó không bị ảnh hưởng bởi miền địa phương (dấu phẩy không thể là dấu phân cách thập phân awkvì nó đã là một ký tự đặc biệt trong cú pháp ở đó ( print 1,2giống như print 1, 2để truyền hai đối số cho print)

zsh

Trong zsh(hỗ trợ số học dấu phẩy động (dấu tách thập phân luôn luôn là dấu chấm)), bạn có rint()hàm toán học để cung cấp cho bạn số nguyên gần nhất dưới dạng float (như in C) và int()cung cấp cho bạn một số nguyên từ float (như in awk). Vì vậy, bạn có thể làm:

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

Hoặc là:

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

Tuy nhiên lưu ý rằng trong khi doubles có thể đại diện cho số lượng rất lớn, số nguyên bị giới hạn hơn nhiều.

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

ksh93

ksh93 là lớp vỏ giống như Bourne đầu tiên hỗ trợ số học dấu phẩy động. ksh93 tối ưu hóa thay thế lệnh bằng cách không sử dụng đường ống hoặc rèn khi các lệnh chỉ là các lệnh dựng sẵn. Vì thế

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

không ngã ba. Hoặc thậm chí tốt hơn:

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

điều này cũng không làm ngã ba nhưng cũng không gặp phải rắc rối nào khi tạo môi trường subshell giả.

Bạn cũng có thể làm:

i=$((rint(f)))

Nhưng hãy cẩn thận với:

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

Bạn cũng có thể làm:

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

Nhưng thích cho zsh:

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

Coi chừng ksh93số học dấu phẩy động tôn vinh cài đặt dấu phân cách thập phân ở miền địa phương (mặc dù đó ,là toán tử toán học ( $((1,2))sẽ là 6/5 ở miền địa phương Pháp / Đức ... và tương tự $((1, 2)), đó là 2 trong một ngôn ngữ tiếng Anh) .

yash

yash cũng hỗ trợ số học dấu phẩy động nhưng không có các hàm toán học như ksh93/ zsh's rint(). Bạn có thể chuyển đổi một số thành số nguyên mặc dù bằng cách sử dụng nhị phân hoặc toán tử (cũng hoạt động zshnhưng không hoạt động ksh93). Tuy nhiên, xin lưu ý rằng nó cắt bớt phần thập phân, nó không cung cấp cho bạn số nguyên gần nhất:

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

yash tôn vinh dấu phân cách thập phân của miền địa phương trên đầu ra, nhưng không cho các hằng số nghĩa đen của dấu phẩy động trong các biểu thức số học của nó, điều này có thể gây ra sự ngạc nhiên:

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

Theo cách tốt, bạn có thể sử dụng các hằng số dấu phẩy động trong các tập lệnh sử dụng dấu chấm và không phải lo lắng rằng nó sẽ ngừng hoạt động ở các địa phương khác, nhưng vẫn có thể xử lý các số được người dùng thể hiện miễn là như bạn nhớ làm:

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%.*}đã hoạt động rất tốt trong tập lệnh bash (phiên bản 3.2.57 (1)) của tôi trên máy Mac (phiên bản 10.13.4 (17E199)).
TelamonAegisthus

Công thức đầu tiên của bạn không thành công trong GNU bash 4.4.19, ví dụ: `` `$ echo $ max 32.800 $ printf -v int% .0f" $ Maximum "bash: printf: 32.800: số không hợp lệ` ``
Luís de Sousa

@ LuísdeSousa, miền địa phương của bạn có thể có ,cơ số thập phân. Xem phần vềLC_ALL=C
Stéphane Chazelas

Lưu ý rằng câu trả lời này trả lời một câu hỏi khác: Làm thế nào để loại bỏ chữ số sau dấu chấm không phải những gì được hỏi: chuyện gì vậy đúng cách để làm phao để nguyên .
Isaac

Phương pháp này có nên áp dụng cho số float của bất kỳ số bit nào không? Nó có nên giống nhau cho một float 23 bit hoặc 128 bit mantissa không?
Isaac

21

bc - Một ngôn ngữ máy tính chính xác tùy ý

int (float) sẽ trông giống như:

$ echo "$float/1" | bc 
1234

Để làm tròn tốt hơn sử dụng này:

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

Thí dụ:

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

4
Nhưng float=-2; echo "($float+0.5)/1" | bccho -1.
Stéphane Chazelas

2
Đó gọi là vòng hướng tới + inf. Đó là một trong bốn cách có thể để làm tròn .

5

Câu trả lời trước đó được gửi gần như đúng: "Bạn có thể làm:

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

Nhưng điều đó sẽ loại bỏ phần phân số thay vì cung cấp cho bạn số nguyên gần nhất và điều đó sẽ không hoạt động đối với các giá trị $ float như 1.2e9 hoặc .12 chẳng hạn .... "

Chỉ cần sử dụng ${float%%.*}.

echo ${float%%.*}
1

3

Một hacky rất đơn giản là

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

Sản lượng mẫu

$ float_to_int 32.333
32
$ float_to_int 32
32

Có thể sử dụng sed, nhưng cutgiải pháp đơn giản hơn. Tôi thích sedhoặc cutvì chúng là IMHO khả dụng hơn trên các mục tiêu được nhúng hơn awk(vẫn còn khá phổ biến thông qua busyboxhơn bc).
pevik

0

Liên quan:

  1. Làm thế nào để float thành số nguyên trong awk ,
  2. Làm thế nào để làm tròn số dấu phẩy động trong vỏ?

Bổ sung @ Stéphane Chazelas awk trả lời:

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] )}' "$@";
}

trên awks của tôi, cái sau quay về 0, không trừ đi vô cực (vì "sàn" có thể được hiểu là có nghĩa). Ngoài ra, các vòng đầu tiên giảm một nửa số chẵn. Tôi không chắc liệu điều đó có khác với những gì Bash printflàm hay không, hoặc nếu có bất kỳ lý do nào khác để thích hơn so với nội dung printf.
ilkkachu
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.