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" "$@"
}
Rahul Patil
źródło
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
Stéphane Chazelas
źródło
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
niewinny świat
źródło
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
Jay Mayers
źródło
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
GMaster
źródło
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] )}' "$@";
}
użytkownik
źródło
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