Discussion:
ceil / floor Ergebnis an int zuweisen definiert?
(zu alt für eine Antwort)
Heinz Saathoff
2015-01-21 15:56:57 UTC
Permalink
Raw Message
Hallo,

für eine Rundung will ich das Ergebnis von floor oder ceil an einen
Integer zuweisen und möchte dabei vermeiden, dass es durch
Ungenauigkeiten zu einem um +/- 1 falschen Wert kommt.
Ich habe ein Testprogramm unter Win-XP mit einem alten C-Compiler
getestet und hier hat sich floor/ceil wie gewünscht verhalten.
Aber ist das garantiert?


- Heinz


/******** TESTPROGRAMM *********/
#include <stdio.h>
#include <math.h>

int main(int argc, char *argv[])
{
double dval=0.0;
int ival=0;
while(ival < 1000000) {
dval += 1.000000001;
ival += 1;
int i_floor = floor(dval);
int i_ceil = ceil(dval);
if(ival != i_floor) {
/* kann das vorkommen? */
printf("floor: %d != %d\n", ival, i_floor);
}
if(ival+1 != i_ceil) {
/* oder auch dies? */
printf("ceil: %d != %d\n", ival+1, i_ceil);
}
}//while
return 0;
}//main
Stefan Reuther
2015-01-21 17:46:12 UTC
Permalink
Raw Message
Post by Heinz Saathoff
double dval=0.0;
int ival=0;
while(ival < 1000000) {
dval += 1.000000001;
ival += 1;
int i_floor = floor(dval);
int i_ceil = ceil(dval);
if(ival != i_floor) {
/* kann das vorkommen? */
Ich würde mich mal soweit aus dem Fenster lehnen und zu sagen: ja, das
klappt (=kann nicht vorkommen), solange du keine Wertebereiche verlässt.

Insbesondere hast du in deinem Beispiel recht zivile Wertebereiche. Ein
double hat auf quasi allen praxisrelevanten Plattformen deutlich mehr
Mantissenbits als ein int, salopp gesagt wird der double also immer den
für ceil/floor relevanten Ganzzahlanteil der Zahl komplett speichern.

Das schlägt fehl, wenn der double ganze Zahlen nicht mehr darstellen
kann, und statt 1.00000001 eben 2 addiert.


Stefan
Rainer Weikusat
2015-01-21 18:45:34 UTC
Permalink
Raw Message
Post by Stefan Reuther
Post by Heinz Saathoff
double dval=0.0;
int ival=0;
while(ival < 1000000) {
dval += 1.000000001;
ival += 1;
int i_floor = floor(dval);
int i_ceil = ceil(dval);
if(ival != i_floor) {
/* kann das vorkommen? */
Ich würde mich mal soweit aus dem Fenster lehnen und zu sagen: ja, das
klappt (=kann nicht vorkommen), solange du keine Wertebereiche verlässt.
6.3.1.4|1 definiert 'Umwandlung einer endlichen Fliesskommazahl x in eine
Ganzzahl vom Typ t' als aequivalent zu

(t)trunc(x)

Falls das Resultat von trunc(x) ausserhalb des Wertebereichs von t
liegt, ist die Umwandlung undefiniert.

NB: Das gilt auch, wenn t ein vorzeichenloser Typ ist.
Heinz Saathoff
2015-01-22 07:41:54 UTC
Permalink
Raw Message
Post by Rainer Weikusat
Post by Stefan Reuther
Post by Heinz Saathoff
double dval=0.0;
int ival=0;
while(ival < 1000000) {
dval += 1.000000001;
ival += 1;
int i_floor = floor(dval);
int i_ceil = ceil(dval);
if(ival != i_floor) {
/* kann das vorkommen? */
Ich würde mich mal soweit aus dem Fenster lehnen und zu sagen: ja, das
klappt (=kann nicht vorkommen), solange du keine Wertebereiche verlässt.
6.3.1.4|1 definiert 'Umwandlung einer endlichen Fliesskommazahl x in eine
Ganzzahl vom Typ t' als aequivalent zu
(t)trunc(x)
Im truncate sehe ich ja das Problem. Denn wenn ich
double d = 1.9999999999;
int i = d;
printf("i ist %d\n", i);
bekomme ich 1 und nicht 2.
Wenn also das Ergebnis von floor/ceil nicht exakt darstellbar ist,
könnte es Probleme geben.
Wenn also beispielsweise das Ergebnis von
ceil(2.98) zu 2.99999999999999 statt 3.000000000001 wird,
könnte trunc statt gewünschter 3 nur 2 liefern.
Das war meine Befürchtung.
Post by Rainer Weikusat
Falls das Resultat von trunc(x) ausserhalb des Wertebereichs von t
liegt, ist die Umwandlung undefiniert.
Im Wertebereich von int liegt es bei meiner Anwendung immer.


- Heinz
Helmut Schellong
2015-01-22 07:53:51 UTC
Permalink
Raw Message
[...]
Post by Heinz Saathoff
Post by Rainer Weikusat
(t)trunc(x)
Im truncate sehe ich ja das Problem. Denn wenn ich
double d = 1.9999999999;
int i = d;
printf("i ist %d\n", i);
bekomme ich 1 und nicht 2.
Wenn also das Ergebnis von floor/ceil nicht exakt darstellbar ist,
könnte es Probleme geben.
Wenn also beispielsweise das Ergebnis von
ceil(2.98) zu 2.99999999999999 statt 3.000000000001 wird,
könnte trunc statt gewünschter 3 nur 2 liefern.
Das war meine Befürchtung.
Post by Rainer Weikusat
Falls das Resultat von trunc(x) ausserhalb des Wertebereichs von t
liegt, ist die Umwandlung undefiniert.
Im Wertebereich von int liegt es bei meiner Anwendung immer.
Bei positiven Werten 0.5 vor der Umwandlung addieren.

Ähnliches gibt es bei (a+5)/10.
Die Hälfte des Divisors addieren.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Thomas Rachel
2015-01-22 11:47:53 UTC
Permalink
Raw Message
Post by Heinz Saathoff
Wenn also beispielsweise das Ergebnis von
ceil(2.98) zu 2.99999999999999 statt 3.000000000001 wird,
Beides sollte es nicht. Es sollte zu exakt 3.0 werden. Das geht immer -
ganze Zahlen sind als Zweierpotenz immer ohne Periode darstellbar,
sofern sie nicht die Auflösungsgrenze überschreiten.
Post by Heinz Saathoff
Im Wertebereich von int liegt es bei meiner Anwendung immer.
Der natürlich auch recht unterschiedlich sein kann, je nach System
(LP64, LPI64 etc.)

Um mal konkret zu werden: Bei einem IEEE-Double ist die Mantisse 53 Bit
breit; die größte damit darstellbare Zahl ist also 9007199254740992.0 =
2^53.

Bei einem 32-bittigen Integer paßt also, bei 64 Bit hingegen nicht.


Thomas
Rainer Weikusat
2015-01-22 14:15:21 UTC
Permalink
Raw Message
Post by Heinz Saathoff
Post by Rainer Weikusat
Post by Stefan Reuther
Post by Heinz Saathoff
double dval=0.0;
int ival=0;
while(ival < 1000000) {
dval += 1.000000001;
ival += 1;
int i_floor = floor(dval);
int i_ceil = ceil(dval);
if(ival != i_floor) {
/* kann das vorkommen? */
Ich würde mich mal soweit aus dem Fenster lehnen und zu sagen: ja, das
klappt (=kann nicht vorkommen), solange du keine Wertebereiche verlässt.
6.3.1.4|1 definiert 'Umwandlung einer endlichen Fliesskommazahl x in eine
Ganzzahl vom Typ t' als aequivalent zu
(t)trunc(x)
Im truncate sehe ich ja das Problem. Denn wenn ich
double d = 1.9999999999;
int i = d;
printf("i ist %d\n", i);
bekomme ich 1 und nicht 2.
Wenn also das Ergebnis von floor/ceil nicht exakt darstellbar ist,
könnte es Probleme geben.
Wenn also beispielsweise das Ergebnis von
ceil(2.98) zu 2.99999999999999 statt 3.000000000001 wird,
'ceil' ist definiert als 'berechnet die kleinste Ganzzahl, die nicht
kleiner als das Argument ist'. 'Ganzzahl' wird nicht ausdruecklich
definiert, allerdings (das hier ist kein mathematischer Beweis, mehr die
Sorte von intuitivem Verstaendnis fuer die man, wenn ein Beweise gefragt
war, eine Watschen bekommt[*]) ist der kleinste Bestandteil jeder Ganzahl >
0 1, 1 ist in jedem Basis-N Zahlensystem enthalten und folglich kann
jede Ganzahl immer exakt repraesentiert werden: Nachdem alle anderen
Moeglichkeiten erschoepft wurde, dh, die groesst-moegliche Summe von
Zahlen > 1 und < n, die nicht groesser als die zu repraesentierende Zahl
ist, berechnet wurde, hat man sie entweder dargestellt oder es fehlt
noch eine 1. Mehr kann nicht fehlen, sonst haette man die anfaengliche
Summe falsch berechnet, und weniger auch nicht.

[*] *duck*
Rainer Weikusat
2015-01-22 14:33:39 UTC
Permalink
Raw Message
Rainer Weikusat <***@mobileactivedefense.com> writes:

[...]
'Ganzzahl' wird nicht ausdruecklich definiert, allerdings (das hier
ist kein mathematischer Beweis, mehr die Sorte von intuitivem
Verstaendnis fuer die man, wenn ein Beweise gefragt
war, eine Watschen bekommt[*]) ist der kleinste Bestandteil jeder Ganzahl >
0 1, 1 ist in jedem Basis-N Zahlensystem enthalten und folglich kann
jede Ganzahl immer exakt repraesentiert werden: Nachdem alle anderen
Moeglichkeiten erschoepft wurde, dh, die groesst-moegliche Summe von
Zahlen > 1 und < n, die nicht groesser als die zu repraesentierende Zahl
ist, berechnet wurde, hat man sie entweder dargestellt oder es fehlt
noch eine 1. Mehr kann nicht fehlen, sonst haette man die anfaengliche
Summe falsch berechnet, und weniger auch nicht.
Das ist jetzt allerdings totaler Quatsch :-). Ein etwas besserer Versuch
waere: Sei x eine ganze Zahl >= 0, n eine ganze Zahl > 0 und y eine
ganze Zahl >= 0 dann gibt es immer ein z >=0 und < n so das

x = y * n + z

gilt.
Stefan Ram
2015-01-25 05:21:43 UTC
Permalink
Raw Message
Post by Heinz Saathoff
Wenn also beispielsweise das Ergebnis von
ceil(2.98) zu 2.99999999999999 statt 3.000000000001 wird,
Solange eine nicht-negative Zahl so klein ist, daß
Nachkommastellen noch dargestellt werden können, kann auch
die Zahl ohne Nachkommastellen exakt dargestellt werden
floor(2.98) ergibt also immer genau 2.0.

Das sollte der Fall sein wenn sie weniger als DBL_DECIMAL_DIG
Stellen hat, wobei mir die Definition von DBL_DECIMAL_DIG und
DBL_DIG im Standard allerdings nicht genau verständlich ist.

Thomas Richter
2015-01-22 11:05:50 UTC
Permalink
Raw Message
Post by Heinz Saathoff
Hallo,
für eine Rundung will ich das Ergebnis von floor oder ceil an einen
Integer zuweisen und möchte dabei vermeiden, dass es durch
Ungenauigkeiten zu einem um +/- 1 falschen Wert kommt.
Ich habe ein Testprogramm unter Win-XP mit einem alten C-Compiler
getestet und hier hat sich floor/ceil wie gewünscht verhalten.
Aber ist das garantiert?
Wenn es sich um einen C99-Compiler handelt und die Architektur
IEEE 754-Fließkommazahlen implementiert (und ja, das tun fast alle,
außer wirklich exotisches Zeug), dann schon. IEEE 754-Doubles haben 53
Mantissa-Bits und überdecken damit den Bereich von int, also alles, was
sich in einem int darstellen lässt, kann ein double ohne
Genauigkeitsverlust auch darstellen. (Bei float ist das nicht so).

Insofern führt ein floor() mit einer nachfolgenden "truncation" auf int
auf diesen Platformen zum gewünschten Erfolg.

Eine alternative Möglichkeit wäre noch, sich auf floor() und ceil()
nicht zu verlassen, sondern die vom Standard definierte Umwandlung von
double auf int zu werden. Diese implementiert allerdings ein "round to
zero", was für positive Zahlen einem floor() und für negative Zahlen
einem ceil() entspricht. Entsprechend müsste man hier eine
Fallunterscheidung auf positiv-negativ treffen.

double d = ...;
int x = (d >= 0.0)?((int)(d)):(-(int)(-d));

entspräche einem floor().

Für nicht als int-repräsentierbare Werte ist das Ergebnis undefiniert
(also insbesondere für INF oder NAN). Ob das bei Dir vorkommt müsstest
Du nachprüfen.
David Seppi
2015-01-22 11:54:09 UTC
Permalink
Raw Message
Post by Thomas Richter
double d = ...;
int x = (d >= 0.0)?((int)(d)):(-(int)(-d));
entspräche einem floor().
Das schaut für mich wie ein round to zero aus.
Für d = -3.5 passiert folgendes:
-(int)(-d) = -(int)(3.5) = -3.

Dein obiges Konstrukt macht das gleiche wie:
int x = (int)(d);
--
David Seppi
1220 Wien
Thomas Richter
2015-01-22 12:35:58 UTC
Permalink
Raw Message
Post by David Seppi
Post by Thomas Richter
double d = ...;
int x = (d >= 0.0)?((int)(d)):(-(int)(-d));
entspräche einem floor().
Das schaut für mich wie ein round to zero aus.
-(int)(-d) = -(int)(3.5) = -3.
int x = (int)(d);
Mea culpa, Du hast recht. Aus einem Round to Zero ein floor() zu machen
ist leider nicht ganz so trivial.
Stefan Ram
2015-01-25 05:04:16 UTC
Permalink
Raw Message
Post by Heinz Saathoff
getestet und hier hat sich floor/ceil wie gewünscht verhalten.
Aber ist das garantiert?
Die Funktion floor/ceil berechnet den größten/kleinsten
ganzzahligen Wert, der nicht größer/kleiner als der
Wert des Argumentes ist.

Wird ein endlicher Wert eines reellen Gleitkommatypes in
einen ganzzahligen Typ ungleich _Bool gewandelt, werden die
Nachkommastellen abgeschnitten (in Richtung zu 0). Kann der
Wert des ganzzahligen Anteils in dem ganzzahligen Typ dann
nicht dargestellt werden, ist das Verhalten undefiniert.
Loading...