Rundung der Ganzzahldivision (anstatt abzuschneiden)

75

Ich war neugierig zu wissen, wie ich eine Zahl auf die nächste ganze Zahl runden kann. Zum Beispiel, wenn ich hätte:

int a = 59 / 4;

das wäre 14,75, wenn es im Gleitkomma berechnet würde; Wie kann ich das Ergebnis als 15 in "a" speichern?

Dave
quelle
1
Bitte klären Sie: Das nächste Zehntel (14,8) oder die nächste ganze Zahl (15)?
Carl Smotricz
2
@Carl: Da a ein int ist, muss es auf die nächste ganze Zahl gerundet werden, nicht wahr?
Jonathan Leffler
3
@ Jonathan: Sieht so aus, aber warum dann in der Problemstellung "zum nächsten Zehntel" sagen? Entweder ist die Aussage falsch oder es gibt etwas, das das OP tun möchte, das er nicht klar spezifiziert. Das ist die Idee hinter der Bitte um Klarstellung.
Carl Smotricz
@ Carl: hmm, ja, guter Punkt! Ich bin nicht sicher, warum er nach Zehnteln fragt, wenn er das Ergebnis in einer ganzen Zahl speichert.
Jonathan Leffler
Zusätzlich zu meinen C-Makro- und gcc-Anweisungsausdruckversionen habe ich gerade eine C ++ - Funktionsvorlagenversion hinzugefügt , mit Typprüfung, um sicherzustellen, dass nur ganzzahlige Typen verwendet werden, falls dies für ein Hardcore-C ++ - Programm mit Makros erforderlich ist finster auf: ಠ╭╮ಠ stackoverflow.com/questions/2422712/… . (Und die Quelle für das ASCII-Stirnrunzeln: (͡ ° ͜ʖ ͡ °) )
Gabriel Staples

Antworten:

49
int a = 59.0f / 4.0f + 0.5f;

Dies funktioniert nur bei der Zuweisung zu einem int, da nach dem '.' Alles verworfen wird.

Bearbeiten: Diese Lösung funktioniert nur in den einfachsten Fällen. Eine robustere Lösung wäre:

unsigned int round_closest(unsigned int dividend, unsigned int divisor)
{
    return (dividend + (divisor / 2)) / divisor;
}
0xC0DEFACE
quelle
23
Beachten Sie, dass dies eine FLOATING-Zeigerlösung ist. Ich empfehle aus vielen Gründen, die Ganzzahloperation zu verwenden.
Yousf
11
Denn auf Systemen ohne FPU macht dies wirklich, wirklich, schlechten Code.
Michael Dorgan
7
das ist das Problem. Die Frage des OP ist ohne Verwendung eines Gleitkommas lösbar und hängt daher nicht davon ab, ob die FPU-Unterstützung vorhanden oder gut ist. Außerdem schneller (für den Fall, dass viele davon berechnet werden müssen) auf den meisten Architekturen, einschließlich solcher mit ansonsten fantastischer FPU-Unterstützung. Beachten Sie auch, dass Ihre Lösung für größere Zahlen problematisch sein kann, bei denen Floats die angegebenen ganzzahligen Werte nicht genau darstellen können.
Sean Middleditch
8
-1, dies gibt die falsche Antwort für viele Werte, wenn sizeof (int)> = sizeof (float). Beispielsweise verwendet ein 32-Bit-Float einige Bits, um den Exponenten darzustellen, und kann daher nicht jedes 32-Bit-Int genau darstellen. Also 12584491/3 = 4194830.333 ..., was auf 4194830 abrunden sollte, aber auf meiner Maschine, die 12584491 nicht genau in einem Float darstellen kann, rundet die obige Formel auf 4194831 auf, was falsch ist. Double ist sicherer.
Adrian McCarthy
1
Die Frage ist wirklich, warum Sie solche Werte als ganzzahligen Typ darstellen möchten. double hält perfekte ganze Zahlen bis zu 2 ^ 53 und ermöglicht es Ihnen, einfach anzugeben, wie Fehler gerundet werden.
Malcolm McLean
135

Die Standardsprache für die Ganzzahlrundung lautet:

int a = (59 + (4 - 1)) / 4;

Sie addieren den Divisor minus eins zur Dividende.

Jonathan Leffler
quelle
8
Was ist, wenn Sie eine mathematische Runde durchführen möchten (14,75 bis 15, 14,25 bis 14)?
Chris Lutz
11
Ugh ... dann muss man denken ... addiere (n - 1) / 2, mehr oder weniger. Für n == 4 soll x% n ∈ {0, 1} abgerundet und x% n ∈ {2, 3} aufgerundet werden. Sie müssen also 2 hinzufügen, was n / 2 ist. Für n == 5 möchten Sie, dass x% n ∈ {0, 1, 2} und x% n ∈ {3, 4} aufrunden , also müssen Sie noch einmal 2 hinzufügen ... daher : int i = (x + (n / 2)) / n;?
Jonathan Leffler
7
Diese Methode funktioniert positiv int. Wenn der Divisor oder die Dividende jedoch negativ ist, wird eine falsche Antwort ausgegeben. Der Hinweis auf @caf funktioniert auch nicht.
chux
8
Der (Original-) Titel und die Frage stellten zwei verschiedene Dinge. Der Titel sagte Aufrunden (was Sie beantwortet haben), aber der Körper sagt Aufrunden auf das Nächste (was die akzeptierte Antwort versucht).
Adrian McCarthy
5
Nur hüte dich , dass c = (INT_MAX + (4 - 1)) / 4;gibt c = -536870911zu Integer - Überlauf durch ...
KrisWebDev
55

Ein Code, der für jedes Zeichen in Dividende und Divisor funktioniert:

int divRoundClosest(const int n, const int d)
{
  return ((n < 0) ^ (d < 0)) ? ((n - d/2)/d) : ((n + d/2)/d);
}

Als Antwort auf einen Kommentar "Warum funktioniert das eigentlich?" Können wir dies auseinander brechen. Beachten Sie zunächst, dass n/ddies der Quotient wäre, der jedoch gegen Null abgeschnitten und nicht gerundet wird. Sie erhalten ein gerundetes Ergebnis, wenn Sie dem Zähler vor dem Teilen die Hälfte des Nenners hinzufügen, jedoch nur, wenn Zähler und Nenner dasselbe Vorzeichen haben. Wenn sich die Vorzeichen unterscheiden, müssen Sie vor dem Teilen die Hälfte des Nenners abziehen. Alles zusammen:

(n < 0) is false (zero) if n is non-negative
(d < 0) is false (zero) if d is non-negative
((n < 0) ^ (d < 0)) is true if n and d have opposite signs
(n + d/2)/d is the rounded quotient when n and d have the same sign
(n - d/2)/d is the rounded quotient when n and d have opposite signs

Wenn Sie ein Makro bevorzugen:

#define DIV_ROUND_CLOSEST(n, d) ((((n) < 0) ^ ((d) < 0)) ? (((n) - (d)/2)/(d)) : (((n) + (d)/2)/(d)))

Das Linux-Kernel-Makro DIV_ROUND_CLOSEST funktioniert nicht für negative Teiler!

BEARBEITEN: Dies funktioniert ohne Überlauf:

int divRoundClosest( int A, int B )
{
if(A<0)
    if(B<0)
        return (A + (-B+1)/2) / B + 1;
    else
        return (A + ( B+1)/2) / B - 1;
else
    if(B<0)
        return (A - (-B+1)/2) / B - 1;
    else
        return (A - ( B+1)/2) / B + 1;
}
Ericbn
quelle
8
Abgesehen von intWerten nahe min / max int ist dies die bisher beste Lösung.
chux
1
Warum funktioniert das eigentlich? Was ist das mathematische Konzept dahinter?
Tobias Marschall
@TobiasMarschall Dies entspricht floor(n / d + 0.5), wobei n und d Floats sind.
17.
22

Sie sollten stattdessen Folgendes verwenden:

int a = (59 - 1)/ 4 + 1;

Ich gehe davon aus, dass Sie wirklich versuchen, etwas allgemeineres zu tun:

int divide(x, y)
{
   int a = (x -1)/y +1;

   return a;
}

x + (y-1) kann überlaufen und zu einem falschen Ergebnis führen. während x - 1 nur unterläuft, wenn x = min_int ...

WayneJ
quelle
1
61,0 / 30,0 = 2,03333 (3). Die
Aufrundung
3
warum @ nad2000 würde 2,0333 .. gerundet auf 2 sein?
Gurgeh
8
Dies funktioniert nicht, wenn x = 0. Das beabsichtigte Ergebnis der x / y-Aufrundung, wenn x = 0 ist 0. Diese Lösung ergibt jedoch ein Ergebnis von 1. Die andere Lösung liefert die richtige Antwort.
David
Eigentlich ist diese Antwort überhaupt nicht richtig. Es funktioniert für einige Zahlen, schlägt aber bei einigen fehl. Siehe meine bessere (ich hoffe) Antwort später im Thread.
WayneJ
Wenn diese Antwort falsch ist, warum würden Sie sie dann nicht einfach löschen?
Auf Wiedersehen SE
13

(Bearbeitet) Das Runden von Ganzzahlen mit Gleitkomma ist die einfachste Lösung für dieses Problem. Je nach Problemstellung ist dies jedoch möglich. Beispielsweise kann in eingebetteten Systemen die Gleitkomma-Lösung zu kostspielig sein.

Dies mit ganzzahliger Mathematik zu tun, erweist sich als schwierig und ein wenig unintuitiv. Die erste veröffentlichte Lösung funktionierte für das Problem, für das ich sie verwendet hatte, in Ordnung, aber nachdem ich die Ergebnisse über den Bereich von ganzen Zahlen charakterisiert hatte, stellte sich heraus, dass sie im Allgemeinen sehr schlecht war. Das Durchsehen mehrerer Bücher über Bit Twiddling und eingebettete Mathematik liefert nur wenige Ergebnisse. Ein paar Notizen. Erstens habe ich nur auf positive ganze Zahlen getestet, meine Arbeit beinhaltet keine negativen Zähler oder Nenner. Zweitens ist ein umfassender Test von 32-Bit-Ganzzahlen rechenintensiv. Daher habe ich mit 8-Bit-Ganzzahlen begonnen und dann sichergestellt, dass ich mit 16-Bit-Ganzzahlen ähnliche Ergebnisse erzielt habe.

Ich begann mit den 2 Lösungen, die ich zuvor vorgeschlagen hatte:

#define DIVIDE_WITH_ROUND(N, D) (((N) == 0) ? 0:(((N * 10)/D) + 5)/10)

#define DIVIDE_WITH_ROUND(N, D) (N == 0) ? 0:(N - D/2)/D + 1;

Mein Gedanke war, dass die erste Version mit großen Zahlen überlaufen würde und die zweite mit kleinen Zahlen überlaufen würde. Ich habe 2 Dinge nicht berücksichtigt. 1.) Das 2. Problem ist tatsächlich rekursiv, da Sie D / 2 richtig runden müssen, um die richtige Antwort zu erhalten. 2.) Im ersten Fall laufen Sie häufig über und dann unter, die beiden heben sich gegenseitig auf. Hier ist ein Fehlerdiagramm der beiden (falschen) Algorithmen:Teilen Sie mit Round1 8 Bit x = Zähler y = Nenner

Dieses Diagramm zeigt, dass der erste Algorithmus nur für kleine Nenner falsch ist (0 <d <10). Unerwartet handhabt es große Zähler tatsächlich besser als die 2. Version.

Hier ist eine Darstellung des 2. Algorithmus: 8 Bit vorzeichenbehaftete Zahlen 2. Algorithmus.

Wie erwartet schlägt es für kleine Zähler fehl, aber auch für mehr große Zähler als die 1. Version.

Dies ist eindeutig der bessere Ausgangspunkt für eine korrekte Version:

#define DIVIDE_WITH_ROUND(N, D) (((N) == 0) ? 0:(((N * 10)/D) + 5)/10)

Wenn Ihr Nenner> 10 ist, funktioniert dies korrekt.

Für D == 1 wird ein Sonderfall benötigt, geben Sie einfach N zurück. Für D == 2, = N / 2 + (N & 1) // wird ein Sonderfall benötigt.

D> = 3 hat auch Probleme, wenn N groß genug wird. Es stellt sich heraus, dass größere Nenner nur Probleme mit größeren Zählern haben. Für eine 8-Bit-Nummer mit Vorzeichen sind die Problempunkte

if (D == 3) && (N > 75))
else if ((D == 4) && (N > 100))
else if ((D == 5) && (N > 125))
else if ((D == 6) && (N > 150))
else if ((D == 7) && (N > 175))
else if ((D == 8) && (N > 200))
else if ((D == 9) && (N > 225))
else if ((D == 10) && (N > 250))

(D / N für diese zurückgeben)

Im Allgemeinen ist der Punkt, an dem ein bestimmter Zähler schlecht wird, irgendwo in der Nähe
N > (MAX_INT - 5) * D/10

Dies ist nicht genau, aber nah. Wenn Sie mit 16-Bit- oder größeren Zahlen arbeiten, beträgt der Fehler <1%, wenn Sie in diesen Fällen nur eine C-Division (Kürzung) durchführen.

Für 16-Bit-Nummern mit Vorzeichen wären die Tests

if ((D == 3) && (N >= 9829))
else if ((D == 4) && (N >= 13106))
else if ((D == 5) && (N >= 16382))
else if ((D == 6) && (N >= 19658))
else if ((D == 7) && (N >= 22935))
else if ((D == 8) && (N >= 26211))
else if ((D == 9) && (N >= 29487))
else if ((D == 10) && (N >= 32763))

Natürlich würde für vorzeichenlose Ganzzahlen MAX_INT durch MAX_UINT ersetzt. Ich bin sicher, dass es eine genaue Formel zur Bestimmung des größten N gibt, das für ein bestimmtes D und eine bestimmte Anzahl von Bits funktioniert, aber ich habe keine Zeit mehr, um an diesem Problem zu arbeiten ...

(Mir scheint dieses Diagramm im Moment zu fehlen, ich werde es später bearbeiten und hinzufügen.) Dies ist ein Diagramm der 8-Bit-Version mit den oben genannten Sonderfällen :! [8-Bit mit Sonderfällen für 3 signiert0 < N <= 10

Beachten Sie, dass für 8 Bit der Fehler 10% oder weniger für alle Fehler im Diagramm beträgt, 16 Bit <0,1%.

WayneJ
quelle
1
Du hast Recht. Das erste Makro war falsch (ich habe es auf die harte Tour herausgefunden.) Es stellte sich heraus, dass es schwieriger war, als ich erwartet hatte. Ich habe den Beitrag aktualisiert, um die Unrichtigkeit anzuerkennen, und einige weitere Diskussionen aufgenommen.
WayneJ
7

Wie geschrieben, führen Sie eine Ganzzahlarithmetik durch, bei der alle Dezimalergebnisse automatisch abgeschnitten werden. Um eine Gleitkomma-Arithmetik durchzuführen, ändern Sie entweder die Konstanten in Gleitkommawerte:

int a = round(59.0 / 4);

Oder wandeln Sie sie in einen floatoder einen anderen Gleitkommatyp um:

int a = round((float)59 / 4);

In beiden Fällen müssen Sie die endgültige Rundung mit der round()Funktion im math.hHeader durchführen. #include <math.h>Verwenden Sie daher unbedingt einen C99-kompatiblen Compiler.

Chris Lutz
quelle
Ein typisches float(IEEE) begrenzt den nützlichen Bereich dieser Lösung auf abs (a / b) <16.777.216.
chux
Oder vielleicht lround.
Ciro Santilli 法轮功 冠状 病 六四 事件 18
5

Vom Linux-Kernel (GPLv2):

/*
 * Divide positive or negative dividend by positive divisor and round
 * to closest integer. Result is undefined for negative divisors and
 * for negative dividends if the divisor variable type is unsigned.
 */
#define DIV_ROUND_CLOSEST(x, divisor)(          \
{                           \
    typeof(x) __x = x;              \
    typeof(divisor) __d = divisor;          \
    (((typeof(x))-1) > 0 ||             \
     ((typeof(divisor))-1) > 0 || (__x) > 0) ?  \
        (((__x) + ((__d) / 2)) / (__d)) :   \
        (((__x) - ((__d) / 2)) / (__d));    \
}                           \
)
PauliusZ
quelle
Ist ein typeof()Teil von C oder eine compilerspezifische Erweiterung?
chux
4
@chux: Es ist eine GCC-Erweiterung . Es ist kein Teil von Standard C.
Cornstalks
Eine gute Möglichkeit, in einem Makro nach signierten und nicht signierten Argumenten zu suchen, sodass nicht signierte Argumente den Zweig und zusätzliche Anweisungen vollständig weglassen können.
Peter Cordes
4
#define CEIL(a, b) (((a) / (b)) + (((a) % (b)) > 0 ? 1 : 0))

Ein weiterer nützlicher MAKROS (MUSS HABEN):

#define MIN(a, b)  (((a) < (b)) ? (a) : (b))
#define MAX(a, b)  (((a) > (b)) ? (a) : (b))
#define ABS(a)     (((a) < 0) ? -(a) : (a))
Magnetron
quelle
1
Deine Klammern machen mich schwindelig.
Andrew
10
Sicher, es sieht nach einem schlechten Fall von LISP aus, aber das Weglassen der Klammern um jedes Argument und die anschließende Bewertung von ABS (4 & -1) ist schlechter.
Justin
2
Er will ROUND, nichtCEIL
phuclv
4
int a, b;
int c = a / b;
if(a % b) { c++; }

Wenn Sie prüfen, ob ein Rest vorhanden ist, können Sie den Quotienten der Ganzzahldivision manuell aufrunden.

user3707766
quelle
1
Wird dies nicht zu oft aufgerundet, wenn der Divisor etwas anderes als 2 ist?
Doug McClean
Dies ist immer eine Rundung wie eine ceilFunktion, keine richtige Rundung
phuclv
3

Hier ist meine Lösung. Ich mag es, weil ich es besser lesbar finde und weil es keine Verzweigung hat (weder wenn noch ternäre).

int32_t divide(int32_t a, int32_t b) {
  int32_t resultIsNegative = ((a ^ b) & 0x80000000) >> 31;
  int32_t sign = resultIsNegative*-2+1;
  return (a + (b / 2 * sign)) / b;
}

Vollständiges Testprogramm, das das beabsichtigte Verhalten veranschaulicht:

#include <stdint.h>
#include <assert.h>

int32_t divide(int32_t a, int32_t b) {
  int32_t resultIsNegative = ((a ^ b) & 0x80000000) >> 31;
  int32_t sign = resultIsNegative*-2+1;
  return (a + (b / 2 * sign)) / b;
}

int main() {
  assert(divide(0, 3) == 0);

  assert(divide(1, 3) == 0);
  assert(divide(5, 3) == 2);

  assert(divide(-1, 3) == 0);
  assert(divide(-5, 3) == -2);

  assert(divide(1, -3) == 0);
  assert(divide(5, -3) == -2);

  assert(divide(-1, -3) == 0);
  assert(divide(-5, -3) == 2);
}
Rasmus Rønn Nielsen
quelle
Das &In ((a ^ b) & 0x80000000) >> 31;ist redundant, da die niedrigen Bits sowieso nach der Verschiebung weggeworfen werden
phuclv
3

Ausleihen bei @ericbn definiere ich wie

#define DIV_ROUND_INT(n,d) ((((n) < 0) ^ ((d) < 0)) ? (((n) - (d)/2)/(d)) : (((n) + (d)/2)/(d)))
or if you work only with unsigned ints
#define DIV_ROUND_UINT(n,d) ((((n) + (d)/2)/(d)))
Zevero
quelle
2

TLDR: Hier ist ein Makro; benutze es!

// To do (numer/denom), rounded to the nearest whole integer, use:
#define ROUND_DIVIDE(numer, denom) (((numer) + (denom) / 2) / (denom))

Anwendungsbeispiel:

int num = ROUND_DIVIDE(13,7); // 13/7 = 1.857 --> rounds to 2, so num is 2

Vollständige Antwort:

Einige dieser Antworten sehen verrückt aus! Codeface hat es aber geschafft! (Siehe die Antwort von @ 0xC0DEFACE hier ). Ich mag das typfreie Ausdrucksformular für Makros oder gcc-Anweisungen gegenüber dem Funktionsformular sehr, daher habe ich diese Antwort mit einer detaillierten Erklärung meiner Arbeit (dh warum dies mathematisch funktioniert) geschrieben und in zwei Formulare unterteilt ::

1. Makroformular mit ausführlichem Kommentar zur Erklärung des Ganzen:

/// @brief      ROUND_DIVIDE(numerator/denominator): round to the nearest whole integer when doing 
///             *integer* division only
/// @details    This works on *integers only* since it assumes integer truncation will take place automatically
///             during the division! 
/// @notes      The concept is this: add 1/2 to any number to get it to round to the nearest whole integer
///             after integer trunction.
///             Examples:  2.74 + 0.5 = 3.24 --> 3 when truncated
///                        2.99 + 0.5 = 3.49 --> 3 when truncated
///                        2.50 + 0.5 = 3.00 --> 3 when truncated
///                        2.49 + 0.5 = 2.99 --> 2 when truncated
///                        2.00 + 0.5 = 2.50 --> 2 when truncated
///                        1.75 + 0.5 = 2.25 --> 2 when truncated
///             To add 1/2 in integer terms, you must do it *before* the division. This is achieved by 
///             adding 1/2*denominator, which is (denominator/2), to the numerator before the division.
///             ie: `rounded_division = (numer + denom/2)/denom`.
///             ==Proof==: 1/2 is the same as (denom/2)/denom. Therefore, (numer/denom) + 1/2 becomes 
///             (numer/denom) + (denom/2)/denom. They have a common denominator, so combine terms and you get:
///             (numer + denom/2)/denom, which is the answer above.
/// @param[in]  numerator   any integer type numerator; ex: uint8_t, uint16_t, uint32_t, int8_t, int16_t, int32_t, etc
/// @param[in]  denominator any integer type denominator; ex: uint8_t, uint16_t, uint32_t, int8_t, int16_t, int32_t, etc
/// @return     The result of the (numerator/denominator) division rounded to the nearest *whole integer*!
#define ROUND_DIVIDE(numerator, denominator) (((numerator) + (denominator) / 2) / (denominator))

2. Formular für den Ausdruck der GCC-Anweisung :

Sehen Sie ein wenig mehr auf gcc - Anweisung Ausdrücke hier .

/// @brief      *gcc statement expression* form of the above macro
#define ROUND_DIVIDE2(numerator, denominator)               \
({                                                          \
    __typeof__ (numerator) numerator_ = (numerator);        \
    __typeof__ (denominator) denominator_ = (denominator);  \
    numerator_ + (denominator_ / 2) / denominator_;         \
})

3. Formular für C ++ - Funktionsvorlagen:

(Hinzugefügt im März / April 2020)

#include <limits>

// Template form for C++ (with type checking to ensure only integer types are passed in!)
template <typename T>
T round_division(T numerator, T denominator)
{
    // Ensure only integer types are passed in, as this round division technique does NOT work on
    // floating point types since it assumes integer truncation will take place automatically
    // during the division!
    // - The following static assert allows all integer types, including their various `const`,
    //   `volatile`, and `const volatile` variations, but prohibits any floating point type
    //   such as `float`, `double`, and `long double`. 
    // - Reference page: https://en.cppreference.com/w/cpp/types/numeric_limits/is_integer
    static_assert(std::numeric_limits<T>::is_integer, "Only integer types are allowed"); 
    return (numerator + denominator/2)/denominator;
}

Führen Sie einen Teil dieses Codes hier aus und testen Sie ihn:

  1. OnlineGDB: Ganzzahlrundung während der Division

Verwandte Antworten:

  1. Festkomma-Arithmetik in der C-Programmierung - In dieser Antwort gehe ich auf die Ganzzahlrundung auf die nächste ganze Ganzzahl ein, dann auf die zehnte Stelle (1 Dezimalstelle rechts von der Dezimalstelle), die hundertste Stelle (2 Dezimalstellen), die tausendste Stelle ( 3 Dez. Ziffern) usw. Suchen Sie in der Antwort nach dem Abschnitt in meinen Codekommentaren, BASE 2 CONCEPT:um weitere Details zu erhalten!
  2. Eine verwandte Antwort von mir auf die Aussagen von gcc: MIN und MAX in C.
  3. Die Funktionsform davon mit festen Typen: Rundung der Ganzzahldivision (statt Abschneiden)
  4. Wie verhält sich die Ganzzahldivision?
  5. Befolgen Sie zum Aufrunden anstelle der nächsten Ganzzahl das gleiche Muster: Runden der Ganzzahldivision (anstatt abzuschneiden)

Verweise:

  1. https://www.tutorialspoint.com/cplusplus/cpp_templates.htm

todo: teste dies auf negative Eingaben und aktualisiere diese Antwort, wenn es funktioniert:

#define ROUND_DIVIDE(numer, denom) ((numer < 0) != (denom < 0) ? ((numer) - (denom) / 2) / (denom) : ((numer) + (denom) / 2) / (denom))
Gabriel Staples
quelle
Auf jeden Fall TLDR
chqrlie
Kopieren Sie die Codeblöcke. Löschen Sie alle Kommentare. Jeder der 3 Ansätze entspricht 1 bis 4 Zeilen. Die Details darüber, was wirklich passiert und wie es funktioniert, verdienen jedoch die ausführlichen Erklärungen als Lehrplattform.
Gabriel Staples
1
Die kurze Antwort ist fehlerhaft: ROUND_DIVIDE(-3 , 4)ausgewertet zu 0, was nicht die nächste ganze Zahl ist. Die ausführlichen Erklärungen sprechen dieses Problem überhaupt nicht an. (int)round(-3.0 / 4.0)würde zu bewerten -1.
Chqrlie
1
Und wenn ich das falsch angegeben habe, werde ich es herausfinden, wenn ich es reparieren gehe. Es ist super spät. Ich werde auch meinen Testfällen negative Zahlen hinzufügen.
Gabriel Staples
1
Ich habe das nicht vergessen. Meine Vermutung war richtig, in bestimmten Fällen die Addition gegen die Subtraktion auszutauschen, was ein logisches XOR erfordert, das auf der Negativität jeder Eingabe basiert. Ich bin dabei, diese Antwort zuerst vollständig auf meinem lokalen Computer neu zu schreiben, sie so zu korrigieren, dass sie negative Zahlen verarbeitet, und Makros zum Abrunden und Aufrunden hinzuzufügen. Wenn ich fertig bin, habe ich so etwas wie DIVIDE_ROUNDUP(), DIVIDE_ROUNDDOWN()und DIVIDE_ROUNDNEAREST(), die alle sowohl positive als auch negative Ganzzahl-Eingaben verarbeiten. Hoffentlich gewinne ich dann deine Gegenstimme. Ich werde diese sicherlich selbst verwenden.
Gabriel Staples
1
int divide(x,y){
 int quotient = x/y;
 int remainder = x%y;
 if(remainder==0)
  return quotient;
 int tempY = divide(y,2);
 if(remainder>=tempY)
  quotient++;
 return quotient;
}

zB 59/4 Quotient = 14, TempY = 2, Rest = 3, Rest> = TempY, daher Quotient = 15;

Abhay Lolekar
quelle
1
PS. "also" und "ergo" klingen noch höher als "daher".
Luser Droog
Dies funktioniert bei negativen Zahlen nicht richtig - beachten Sie divide(-59, 4).
Café
1
double a=59.0/4;
int b=59/4;
if(a-b>=0.5){
    b++;
}
printf("%d",b);
  1. Der exakte Float-Wert von 59,0 / 4 sei x (hier ist er 14,750000).
  2. sei die kleinste ganze Zahl kleiner als x y (hier ist es 14)
  3. Wenn xy <0,5 ist, ist y die Lösung
  4. sonst ist y + 1 die Lösung
Siva Dhatra
quelle
Bitte schreiben Sie eine Erklärung, es ist vielleicht nicht für alle trivial.
Mraron
Sieht es jetzt besser aus?
Siva Dhatra
Dies ist die schlechteste Lösung. 2 langsame Divisionen zu machen, ist nichts, was die Leute tun wollen. Und b kann erhalten werden, indem a
abgeschnitten wird,
0

Versuchen Sie es mit der mathematischen Ceil-Funktion, mit der aufgerundet wird. Mathe Decke !

Samuel Santos
quelle
0

Wenn Sie positive ganze Zahlen teilen, können Sie sie nach oben verschieben, dividieren und dann das Bit rechts vom realen b0 überprüfen. Mit anderen Worten, 100/8 ist 12,5, würde aber 12 zurückgeben. Wenn Sie (100 << 1) / 8 tun, können Sie b0 überprüfen und dann aufrunden, nachdem Sie das Ergebnis wieder nach unten verschoben haben.

Bryan
quelle
0

Für einige Algorithmen benötigen Sie eine konsistente Verzerrung, wenn "am nächsten" ein Gleichstand ist.

// round-to-nearest with mid-value bias towards positive infinity
int div_nearest( int n, int d )
   {
   if (d<0) n*=-1, d*=-1;
   return (abs(n)+((d-(n<0?1:0))>>1))/d * ((n<0)?-1:+1);
   }

Dies funktioniert unabhängig vom Vorzeichen des Zählers oder Nenners.


Wenn Sie die Ergebnisse von round(N/(double)D)(Gleitkommadivision und Rundung) abgleichen möchten , finden Sie hier einige Variationen, die alle die gleichen Ergebnisse liefern:

int div_nearest( int n, int d )
   {
   int r=(n<0?-1:+1)*(abs(d)>>1); // eliminates a division
// int r=((n<0)^(d<0)?-1:+1)*(d/2); // basically the same as @ericbn
// int r=(n*d<0?-1:+1)*(d/2); // small variation from @ericbn
   return (n+r)/d;
   }

Hinweis: Die relative Geschwindigkeit von (abs(d)>>1)vs. (d/2)ist wahrscheinlich plattformabhängig.

Brent Bradburn
quelle
Wie von @caf in einem Kommentar zu einer anderen Antwort festgestellt, ist ein Überlauf bei diesem Rundungsansatz ein Risiko (da der Zähler vor der Division geändert wird). Daher ist diese Funktion nicht geeignet, wenn Sie die Bereichsgrenzen von verschieben int.
Brent Bradburn
1
Übrigens, wenn Sie zufällig durch eine Zweierpotenz teilen (was auch einen positiven Teiler impliziert), können Sie die Tatsache ausnutzen, dass das vorzeichenbehaftete Verschiebungsrecht den Effekt einer Teilung mit einer runden in Richtung negativer Unendlichkeit hat ( im Gegensatz zum Divisionsoperator, der gegen Null rundet), um die Verwendung einer bedingten Logik zu vermeiden. So wird die Formel return (n+(1<<shift>>1))>>shift;, die sich zu der Form vereinfacht (n+C)>>shift(wo, C=(1<<shift>>1)wenn shiftzufällig eine Konstante ist.
Brent Bradburn
Das Problem der "mittleren Verzerrung" bezieht sich nur auf den Fall von genau 0,5 zwischen ganzen Zahlen, daher ist es sicher esoterisch. Für eine Art grafische Darstellung möchten Sie beispielsweise eher eine Kontinuität um Null als eine Symmetrie sehen.
Brent Bradburn
0

Im Folgenden wird der Quotient für positive und negative Operanden OHNE Gleitkomma- oder bedingte Verzweigungen korrekt auf die nächste Ganzzahl gerundet (siehe Baugruppenausgabe unten). Nimmt die Komplement-Ganzzahlen von N-Bit 2 an.

#define ASR(x) ((x) < 0 ? -1 : 0)  // Compiles into a (N-1)-bit arithmetic shift right
#define ROUNDING(x,y) ( (y)/2 - (ASR((x)^(y)) & (y)))

int RoundedQuotient(int x, int y)
   {
   return (x + ROUNDING(x,y)) / y ;
   }

Der Wert von ROUNDING hat das gleiche Vorzeichen wie die Dividende (x) und die halbe Größe des Divisors (y). Das Hinzufügen von RUNDEN zur Dividende erhöht somit deren Größe, bevor die ganzzahlige Division den resultierenden Quotienten abschneidet. Hier ist die Ausgabe des gcc-Compilers mit -O3-Optimierung für einen 32-Bit-ARM-Cortex-M4-Prozessor:

RoundedQuotient:                // Input parameters: r0 = x, r1 = y
    eor     r2, r1, r0          // r2 = x^y
    and     r2, r1, r2, asr #31 // r2 = ASR(x^y) & y
    add     r3, r1, r1, lsr #31 // r3 = (y < 0) ? y + 1 : y
    rsb     r3, r2, r3, asr #1  // r3 = y/2 - (ASR(x^y) & y)
    add     r0, r0, r3          // r0 = x + (y/2 - (ASR(x^y) & y)
    sdiv    r0, r0, r1          // r0 = (x + ROUNDING(x,y)) / y
    bx      lr                  // Returns r0 = rounded quotient
Dan Lewis
quelle
0

Einige Alternativen zur Division durch 4

return x/4 + (x/2 % 2);
return x/4 + (x % 4 >= 2)

Oder im Allgemeinen Division durch eine Potenz von 2

return x/y + x/(y/2) % 2;    // or
return (x >> i) + ((x >> i - 1) & 1);  // with y = 2^i

Es funktioniert durch Aufrunden, wenn der Bruchteil ⩾ 0,5, dh die erste Ziffer ⩾ Basis / 2 ist. In der Binärdatei entspricht dies dem Hinzufügen des ersten Bruchbits zum Ergebnis

Diese Methode hat in Architekturen mit einem Flag-Register einen Vorteil, da das Übertragsflag das letzte herausgeschobene Bit enthält . Zum Beispiel auf x86 kann es optimiert werden

shr eax, i
adc eax, 0

Es kann auch problemlos erweitert werden, um vorzeichenbehaftete Ganzzahlen zu unterstützen. Beachten Sie, dass der Ausdruck für negative Zahlen lautet

(x - 1)/y + ((x - 1)/(y/2) & 1)

Wir können dafür sorgen, dass es sowohl für positive als auch für negative Werte funktioniert

int t = x + (x >> 31);
return (t >> i) + ((t >> i - 1) & 1);
phuclv
quelle
0

Der grundlegende Algorithmus zur Rundungsteilung, wie er von früheren Mitwirkenden vorgestellt wurde, besteht darin, dem Zähler vor der Division die Hälfte des Nenners hinzuzufügen. Dies ist einfach, wenn die Eingaben ohne Vorzeichen sind, nicht ganz, wenn es sich um vorzeichenbehaftete Werte handelt. Hier sind einige Lösungen, die von GCC optimalen Code für ARM generieren (thumb-2).

Signiert / nicht signiert

inline int DivIntByUintRnd(int n, uint d)       
{ 
    int sgn = n >> (sizeof(n)*8-1); // 0 or -1
    return (n + (int)(((d / 2) ^ sgn) - sgn)) / (int)d; 
}

Die erste Codezeile repliziert das Zählerzeichenbit durch ein ganzes Wort und erzeugt Null (positiv) oder -1 (negativ). In der zweiten Zeile wird dieser Wert (falls negativ) verwendet, um den Rundungsterm unter Verwendung der Komplementnegation von 2 zu negieren: Komplement und Inkrement. In früheren Antworten wurde eine bedingte Anweisung verwendet oder multipliziert, um dies zu erreichen.

Signiert / Signiert

inline int DivIntRnd(int n, int d)      
{ 
    int rnd = d / 2;
    return (n + ((n ^ d) < 0 ? -rnd : rnd)) / d; 
}

Ich habe festgestellt, dass ich den kürzesten Code mit dem bedingten Ausdruck erhalten habe, aber nur, wenn ich dem Compiler durch Berechnung des Rundungswerts d / 2 geholfen habe. Die Verwendung der Komplementnegation von 2 ist nah:

inline int DivIntRnd(int n, int d)      
{ 
    int sgn = (n ^ d) >> (sizeof(n)*8-1);   // 0 or -1
    return (n + ((d ^ sgn) - sgn) / 2) / d; 
}

Division durch Mächte von 2

Während die ganzzahlige Division gegen Null abschneidet, schneidet die Verschiebung gegen negative Unendlichkeit ab. Dies macht eine Rundungsverschiebung viel einfacher, da Sie den Rundungswert unabhängig vom Vorzeichen des Zählers immer addieren.

inline int ShiftIntRnd(int n, int s)        { return ((n >> (s - 1)) + 1) >> 1; }
inline uint ShiftUintRnd(uint n, int s)     { return ((n >> (s - 1)) + 1) >> 1; }

Der Ausdruck ist derselbe (je nach Typ wird unterschiedlicher Code generiert), sodass ein Makro oder eine überladene Funktion für beide funktionieren kann.

Die traditionelle Methode (die Art und Weise, wie die Rundungsteilung funktioniert) besteht darin, die Hälfte des Teilers 1 << (s-1) zu addieren. Stattdessen verschieben wir eine weniger, fügen eine hinzu und machen dann die letzte Schicht. Dies erspart das Erstellen eines nicht trivialen Werts (auch wenn dieser konstant ist) und das Maschinenregister, in das er eingegeben werden soll.

DosMan
quelle
int sgn = n >> (sizeof(n)*8-1); // 0 or -1: NEIN, das Verhalten ist nicht durch den C-Standard definiert. Sie solltenint sgn = ~(n < 0);
chqrlie
Mein Anliegen ist die Geschwindigkeit und Größe eines ARM-Mikrocontrollers. Der Ausdruck ~(n < 0)erzeugt eine weitere Anweisung. Außerdem funktioniert der ursprüngliche Ausdruck auf jeder Architektur mit 8-Bit-Bytes und Zweierkomplement, was meiner Meinung nach alle modernen Maschinen beschreibt.
DosMan
-1

Ich stieß auf die gleiche Schwierigkeit. Der folgende Code sollte für positive Ganzzahlen funktionieren.

Ich habe es noch nicht kompiliert, aber ich habe den Algorithmus in einer Google-Tabelle (ich weiß, wtf) getestet und es hat funktioniert.

unsigned int integer_div_round_nearest(unsigned int numerator, unsigned int denominator)
{
    unsigned int rem;
    unsigned int floor;
    unsigned int denom_div_2;

    // check error cases
    if(denominator == 0)
        return 0;

    if(denominator == 1)
        return numerator;

    // Compute integer division and remainder
    floor = numerator/denominator;
    rem = numerator%denominator;

    // Get the rounded value of the denominator divided by two
    denom_div_2 = denominator/2;
    if(denominator%2)
        denom_div_2++;

    // If the remainder is bigger than half of the denominator, adjust value
    if(rem >= denom_div_2)
        return floor+1;
    else
        return floor;
}
Frank
quelle
Ich sehe keine Notwendigkeit für if(denominator == 1) return numerator;. Was ist seine Aufgabe?
chux
-1

Sichererer C-Code (sofern Sie keine anderen Methoden zur Behandlung von / 0 haben):

return (_divisor > 0) ? ((_dividend + (_divisor - 1)) / _divisor) : _dividend;

Dies behandelt natürlich nicht die Probleme, die durch einen falschen Rückgabewert aufgrund Ihrer ungültigen Eingabedaten entstehen.

radsdau
quelle