Wie konvertiert man Gleitkommazahlen in Ganzzahlen?

47

Ist dies der richtige Weg, um eine Float-in-Integer-Konvertierung in Bash durchzuführen? Gibt es eine andere Methode?

flotToint() {
    printf "%.0f\n" "$@"
}
Rahul Patil
quelle
5
Was ist richtig"?
Joseph R.
Ich möchte nur sicherstellen, dass ... stimmt das oder gibt es etwas Besseres?
Rahul Patil
2
%.0fwird auf- oder abrunden. Ist es das was du willst? Mit können Sie printf "%d\n" "$@" 2>/dev/nullden Bruch hacken.
ott--

Antworten:

70

Bash

In bash, die wahrscheinlich so gut wie es nur geht. Das nutzt eine eingebaute Shell. Wenn Sie das Ergebnis in einer Variablen benötigen, können Sie die Befehlssubstitution oder die bashspezifische Funktion (die jetzt auch von unterstützt wird zsh) verwenden:

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

Du könntest es tun:

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

Aber das würde den Bruchteil entfernen, anstatt Ihnen die nächste Ganzzahl zu geben, und das würde nicht für Werte $floatwie 1.2e9oder .12zum Beispiel funktionieren .

Beachten Sie auch die möglichen Einschränkungen aufgrund der internen Darstellung von Floats:

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

Sie erhalten eine Ganzzahl, aber es besteht die Möglichkeit, dass Sie diese Ganzzahl nirgendwo verwenden können.

Wie von @BinaryZebra bemerkt, wird es in verschiedenen printfImplementierungen (bash, ksh93, yash, nicht GNU, zsh, dash) vom Gebietsschema (dem Dezimaltrennzeichen, das .oder sein kann ,) beeinflusst.

Wenn Ihre Gleitkommazahlen immer mit dem Punkt als Dezimaltrennzeichen angegeben werden und Sie möchten, dass sie printfunabhängig vom Gebietsschema des Benutzers, der Ihr Skript aufruft, als solches behandelt werden , müssen Sie das Gebietsschema auf C festlegen:

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

Mit yashkönnen Sie auch Folgendes tun:

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

(siehe unten).

POSIX

printf "%.0f\n" 1.1

ist nicht POSIX, da %fes nicht von POSIX unterstützt werden muss.

POSIXly können Sie tun:

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

Dieses ist vom Gebietsschema nicht betroffen (das Komma darf kein Dezimaltrennzeichen sein, awkda es dort bereits ein Sonderzeichen in der Syntax ist ( print 1,2dasselbe gilt für print 1, 2die Übergabe von zwei Argumenten an print).

zsh

In zsh(das Gleitkomma-Arithmetik unterstützt (Dezimaltrennzeichen ist immer der Punkt)) haben Sie die rint()mathematische Funktion, um die nächste Ganzzahl als Gleitkomma (wie in C) und int()eine Ganzzahl aus einem Gleitkomma (wie in awk) anzugeben . Sie können also Folgendes tun:

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

Oder:

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

Beachten Sie jedoch, dass doubles zwar sehr große Zahlen darstellen kann, Ganzzahlen jedoch wesentlich eingeschränkter sind.

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

ksh93

ksh93 war die erste Bourne-ähnliche Shell, die Gleitkomma-Arithmetik unterstützt. ksh93 optimiert die Befehlsersetzung, indem keine Pipe verwendet oder gegabelt wird, wenn die Befehle nur eingebaute Befehle sind. Damit

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

gabelt nicht. Oder noch besser:

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

Das gibt auch nicht nach, macht aber auch nicht die ganze Mühe, eine gefälschte Subshell-Umgebung zu erstellen.

Sie können auch tun:

i=$((rint(f)))

Aber hüte dich vor:

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

Sie könnten auch tun:

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

Aber wie für zsh:

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

Beachten Sie, dass ksh93Gleitkomma-Arithmetik die Dezimaltrennzeichen-Einstellung im Gebietsschema berücksichtigt (auch wenn ,es sich ansonsten um einen mathematischen Operator handelt ( $((1,2))wäre 6/5 in einem französisch / deutschen Gebietsschema und dasselbe wie $((1, 2))2 in einem englischen Gebietsschema). .

yash

yash unterstützt auch Gleitpunktarithmetik aber keine mathematische Funktionen wie haben ksh93/ zsh‚s rint(). Sie können eine Zahl in eine Ganzzahl umwandeln, indem Sie beispielsweise die Binärzahl oder den Operator verwenden (funktioniert auch in, zshaber nicht in ksh93). Beachten Sie jedoch, dass der Dezimalteil abgeschnitten wird und nicht die nächste Ganzzahl angezeigt wird:

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

yash Berücksichtigt das Dezimaltrennzeichen des Gebietsschemas bei der Ausgabe, jedoch nicht die Gleitkomma-Literalkonstanten in den arithmetischen Ausdrücken, was zu Überraschungen führen kann:

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

Es ist in gewisser Weise gut, dass Sie Gleitkommakonstanten in Ihren Skripten verwenden können, die den Punkt verwenden, und sich nicht sorgen müssen, dass er in anderen Gebietsschemata nicht mehr funktioniert, aber dennoch in der Lage ist, die vom Benutzer ausgedrückten Zahlen so lange zu verarbeiten wie du dich erinnerst zu tun:

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
quelle
int=${float%.*}funktionierte sehr gut in meinem bash (version 3.2.57 (1) -release) script auf einem mac (version 10.13.4 (17E199)).
TelamonAegisthus
Ihre erste Formulierung schlägt in GNU bash 4.4.19 fehl, zB: `` $ echo $ maximum 32.800 $ printf -v int% .0f "$ maximum" bash: printf: 32.800: ungültige Zahl `` `
Luís de Sousa
@ LuísdeSousa, dein Gebietsschema hat wahrscheinlich ,den Dezimalpunkt. Siehe den Abschnitt überLC_ALL=C
Stéphane Chazelas
Beachten Sie, dass diese Antwort eine andere Frage beantwortet: So entfernen Sie Ziffern nach dem Punkt, nicht nach dem , was gefragt wurde: Was ist die richtige Methode, um Float zu Integer zu machen .
Isaac
Sollte diese Methode auf Gleitkommazahlen mit einer beliebigen Anzahl von Bits angewendet werden? Sollte es dasselbe für einen Float von 23 Bit oder 128 Bit Mantisse sein?
Isaac
21

bc - Eine beliebige Präzisionsrechnersprache

int (float) sollte so aussehen:

$ echo "$float/1" | bc 
1234

Um besser abzurunden, benutze dies:

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

Beispiel:

$ float=1.49
$ echo "($float+0.5)/1" | bc 
1
$ float=1.50
$ echo "($float+0.5)/1" | bc 
2
unschuldige Welt
quelle
4
Aber es float=-2; echo "($float+0.5)/1" | bcgibt -1.
Stéphane Chazelas
2
Das nennt man rund nach + inf. Dies ist eine von vier möglichen Runden .
5

Die vorige Antwort war fast richtig: "Sie konnten tun:

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

Aber das würde den Bruchteil entfernen, anstatt Ihnen die nächste Ganzzahl zu geben, und das würde nicht für Werte von $ float wie zum Beispiel 1.2e9 oder .12 funktionieren .... "

Verwenden Sie einfach ${float%%.*}.

echo ${float%%.*}
1
Jay Mayers
quelle
3

Eine sehr einfache Hacky ist

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

Beispielausgabe

$ float_to_int 32.333
32
$ float_to_int 32
32
GMaster
quelle
Es wäre möglich zu verwenden sed, aber die cutLösung ist einfacher. Ich bevorzuge sedoder cutda sie meiner Meinung nach mehr auf eingebetteten Zielen verfügbar sind als awk(was immer noch recht häufig über busyboxals ist bc).
Pevik
0

Verbunden:

  1. wie man float in awk zu integer macht ,
  2. Wie rundet man Gleitkommazahlen in der Shell?

Ergänzen @ Stéphane Chazelas awk Antwort:

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] )}' "$@";
}
Benutzer
quelle
bei meinen awks rundet der letztere gegen null, nicht gegen unendlich (als "floor" kann man verstehen). Auch die ersten Runden halbieren sich zu geraden Zahlen. Ich bin mir nicht sicher, ob das anders ist als bei Bash printfoder ob es einen anderen Grund gibt, awk dem eingebauten vorzuziehen printf.
Ilkkachu