Jak przekonwertować liczbę zmiennoprzecinkową na liczbę całkowitą?


47

Czy to właściwy sposób na konwersję typu float to integer w bash? Czy jest jakaś inna metoda?

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

5
Co jest słuszne"?
— Joseph R.

Chcę się tylko upewnić ... czy to prawda, czy może jest coś lepszego?
— Rahul Patil

2
%.0fzaokrągli w górę lub w dół. Czy tego chcesz? Możesz użyć printf "%d\n" "$@" 2>/dev/nulldo posiekania frakcji.
— ott--

Odpowiedzi:


69

grzmotnąć

W bash, to prawdopodobnie tak dobre, jak to możliwe. To używa wbudowanej powłoki. Jeśli potrzebujesz wyniku w zmiennej, możesz użyć podstawienia polecenia lub bashkonkretnego (choć teraz również obsługiwanego przez zsh):

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

Mógłbyś:

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

Ale to usunęłoby część ułamkową zamiast podać najbliższą liczbę całkowitą i nie działałoby to dla wartości $floatpodobnych 1.2e9lub .12na przykład.

Zwróć również uwagę na możliwe ograniczenia wynikające z wewnętrznej reprezentacji liczb zmiennoprzecinkowych:

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

Otrzymujesz liczbę całkowitą, ale są szanse, że nigdzie nie będziesz mógł jej użyć.

Ponadto, jak zauważył @BinaryZebra, w kilku printfimplementacjach (bash, ksh93, yash, a nie GNU, zsh, dash) zależy od ustawień regionalnych (separator dziesiętny, którym może być .lub ,).

Jeśli więc printfliczby zmiennoprzecinkowe są zawsze wyrażane za pomocą kropki jako separatora dziesiętnego, a chcesz, aby była traktowana jako taka niezależnie od ustawień regionalnych użytkownika wywołującego skrypt, musisz ustawić ustawienia regionalne na C:

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

Za pomocą yashmożesz także:

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

(patrz poniżej).

POSIX

printf "%.0f\n" 1.1

nie jest POSIX, ponieważ %fnie musi być obsługiwany przez POSIX.

POSIXly możesz:

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

Ustawienia regionalne nie mają na to wpływu (przecinek nie może być separatorem dziesiętnym, awkponieważ jest już tam znakiem specjalnym w składni ( print 1,2tak samo, jak print 1, 2do przekazania dwóch argumentów print)

zsh

W zsh(który obsługuje arytmetykę zmiennoprzecinkową (separatorem dziesiętnym jest zawsze kropka)), masz funkcję rint()matematyczną, która daje ci najbliższą liczbę całkowitą jako liczbę zmiennoprzecinkową (jak w C) i int()daje ci liczbę całkowitą od liczby zmiennoprzecinkowej (jak w awk). Możesz więc zrobić:

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

Lub:

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

Należy jednak pamiętać, że chociaż doubles może reprezentować bardzo duże liczby, liczby całkowite są znacznie bardziej ograniczone.

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

ksh93

ksh93 była pierwszą powłoką podobną do Bourne'a, która obsługiwała arytmetykę zmiennoprzecinkową. ksh93 optymalizuje podstawianie poleceń, nie używając potoku lub rozwidlenia, gdy polecenia są tylko wbudowanymi poleceniami. Więc

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

nie rozwidla się. Lub nawet lepiej:

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

co również nie rozwidla, ale także nie sprawia kłopotów z tworzeniem fałszywego środowiska podpowłoki.

Możesz także:

i=$((rint(f)))

Ale uważaj na:

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

Możesz także:

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

Ale jak dla zsh:

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

Strzeż się, że ksh93arytmetyka zmiennoprzecinkowa honoruje ustawienie separatora dziesiętnego w ustawieniach narodowych (mimo że w ,przeciwnym razie jest operatorem matematycznym ( $((1,2))w ustawieniach francuskich / niemieckich ... byłoby 6/5, a to samo co $((1, 2))2 w ustawieniach angielskich) .

yash

Yash obsługuje również arytmetyki zmiennoprzecinkowej, ale nie ma funkcji matematycznych, takich jak ksh93/ zsh„s rint(). Możesz jednak przekonwertować liczbę na liczbę całkowitą, używając na przykład operatora binarnego lub operatora (działa również w trybie in, zshale nie w trybie in ksh93). Zauważ jednak, że obcina on część dziesiętną, nie podaje najbliższej liczby całkowitej:

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

yash honoruje separator dziesiętny ustawienia narodowego na wyjściu, ale nie dla stałych literału zmiennoprzecinkowego w wyrażeniach arytmetycznych, co może powodować niespodzianki:

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

Jest to dobre, ponieważ możesz używać stałych zmiennoprzecinkowych w swoich skryptach, które używają kropki i nie musisz się martwić, że przestanie działać w innych lokalizacjach, ale nadal będzie w stanie poradzić sobie z liczbami wyrażonymi przez użytkownika tak długo, jak długo jak pamiętasz, aby zrobić:

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%.*}działał bardzo dobrze w moim skrypcie bash (wersja 3.2.57 (1)) na komputerze Mac (wersja 10.13.4 (17E199)).
— TelamonAegisthus

Pierwsze sformułowanie kończy się niepowodzeniem w GNU bash 4.4.19, np .: `` $ echo $ maximum 32.800 $ printf -v int% .0f "$ maximum" bash: printf: 32.800: nieprawidłowa liczba ``
— Luís de Sousa

@ LuísdeSousa, twoje ustawienia regionalne prawdopodobnie mają ,dziesiętną podstawkę. Zobacz sekcję oLC_ALL=C
— Stéphane Chazelas

Zauważ, że ta odpowiedź odpowiada na inne pytanie: Jak usunąć cyfry po kropce, a nie o to, o co pytano: jaki jest właściwy sposób przestawiania na liczbę całkowitą .
— Izaak

Czy tę metodę należy zastosować do liczb zmiennoprzecinkowych dowolnej liczby bitów? Czy powinno być tak samo dla pływaka 23-bitowego lub 128-bitowego mantysy?
— Izaak

21

bc - Język kalkulatora o dowolnej precyzji

int (float) powinien wyglądać następująco:

$ echo "$float/1" | bc 
1234

Aby zaokrąglić lepiej, użyj tego:

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

Przykład:

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

4
Ale float=-2; echo "($float+0.5)/1" | bcdaje -1.
— Stéphane Chazelas,

2
To się nazywa runda w kierunku + inf. To jeden z czterech możliwych sposobów na zaokrąglenie .

5

Poprzednia przesłana odpowiedź była prawie poprawna: „Możesz zrobić:

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

Ale to usunęłoby część ułamkową zamiast dać najbliższą liczbę całkowitą i nie działałoby to dla wartości $ float jak na przykład 1.2e9 lub .12 .... ”

Po prostu użyj ${float%%.*}.

echo ${float%%.*}
1

3

Jest to bardzo prosty hacky

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

Próbka wyjściowa

$ float_to_int 32.333
32
$ float_to_int 32
32

Byłoby możliwe użycie sed, ale cutrozwiązanie jest prostsze. Wolę sedlub, cutponieważ są IMHO bardziej dostępne na osadzonych celach niż awk(co jest nadal dość powszechne przez busyboxniż bc).
— pevik

0

Związane z:

  1. jak zrobić float do liczby całkowitej w awk ,
  2. Jak zaokrąglać liczby zmiennoprzecinkowe w powłoce?

Odpowiedź uzupełniająca @ 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] )}' "$@";
}

na moich awksach ten ostatni zaokrągla w kierunku zera, a nie do minus nieskończoności (jako „piętro” można rozumieć). Również pierwsze rundy dzielą się o połowę na liczby parzyste. Nie jestem pewien, czy różni się to od tego, co printfrobi Bash , czy też jest jakiś inny powód, aby preferować awk nad wbudowane printf.
— ilkkachu
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.