Meine Antwort auf diese Frage war diese Funktion:
inline bool divisible15(unsigned int x)
{
//286331153 = (2^32 - 1) / 15
//4008636143 = (2^32) - 286331153
return x * 4008636143 <= 286331153;
}
Es funktionierte jedoch perfekt auf meiner Maschine mit dem VS2008-Compiler hier es geht gar nicht.
Hat jemand eine Idee, warum ich auf verschiedenen Compilern unterschiedliche Ergebnisse erhalte? unsigned
Überlauf ist kein undefiniertes Verhalten.
Wichtiger Hinweis: Nach einigen Tests wurde bestätigt, dass es schneller ist, als den Rest der Division durch 15 zu nehmen. (Allerdings nicht bei allen Compilern)
Es ist kein undefiniertes Verhalten, es ist nur eine bahnbrechende Änderung im C-Sprachstandard zwischen C89 und C99.
In C89 passen ganzzahlige Konstanten wie 4008636143 nicht in eine int
oder long int
aber passen in ein unsigned int
sind unsigniert, aber in C99 sind sie beides long int
oder long long int
(je nachdem, welcher der kleinste ist, der den Wert halten kann). Infolgedessen werden alle Ausdrücke mit 64 Bit ausgewertet, was zu einer falschen Antwort führt.
Visual Studio ist ein C89-Compiler und führt daher zum C89-Verhalten, aber dieser Ideone-Link kompiliert im C99-Modus.
Dies wird deutlicher, wenn Sie mit GCC kompilieren -Wall
:
test.c: In function ‘divisible15’:
test.c:8:3: warning: this decimal constant is unsigned only in ISO C90
Aus C89 §3.1.3.2:
Der Typ einer Integer-Konstante ist der erste der entsprechenden Liste, in der ihr Wert dargestellt werden kann. Dezimalzahl ohne Suffix: int, long int, unsigned long int; Oktal oder Hexadezimal ohne Suffix: int, unsigned int, long int, unsigned long int; […]
C99 §6.4.4.1/5-6:
5) Der Typ einer ganzzahligen Konstante ist der erste der entsprechenden Liste, in der ihr Wert dargestellt werden kann.
Suffix | Decimal Constant | Octal or Hexadecimal Constant
-------+------------------+------------------------------
none | int | int
| long int | unsigned int
| long long int | long int
| | unsigned long int
| | long long int
| | unsigned long long int
-------+------------------+------------------------------
[...]
6) Wenn eine ganzzahlige Konstante durch keinen Typ in ihrer Liste dargestellt werden kann, kann sie einen erweiterten ganzzahligen Typ haben, wenn der erweiterte ganzzahlige Typ ihren Wert darstellen kann. Wenn alle Typen in der Liste für die Konstante vorzeichenbehaftet sind, muss der erweiterte ganzzahlige Typ vorzeichenbehaftet sein. […]
Der Vollständigkeit halber hat C++03 tatsächlich ein undefiniertes Verhalten, wenn die Integer-Konstante zu groß ist, um in a zu passen long int
. Aus C++03 §2.13.1/2:
Der Typ eines Integer-Literals hängt von seiner Form, seinem Wert und seinem Suffix ab. Wenn es dezimal ist und kein Suffix hat, hat es den ersten dieser Typen, in denen sein Wert dargestellt werden kann: int
, long int
; wenn der Wert nicht als a dargestellt werden kann long int
, ist das Verhalten undefiniert. Wenn es oktal oder hexadezimal ist und kein Suffix hat, hat es den ersten dieser Typen, in denen sein Wert dargestellt werden kann: int
, unsigned int
, long int
, unsigned
long int
. […]
Das C++11-Verhalten ist identisch mit C99, siehe C++11 §2.14.2/3.
Um sicherzustellen, dass sich der Code konsistent verhält, wenn er entweder als C89, C99, C++03 oder C++11 kompiliert wird, besteht die einfache Lösung darin, die Konstante 4008636143 unsigniert zu machen, indem ihr das Suffix mit hinzugefügt wird u
wie 4008636143u
.
Da Sie verwenden int
Konstanten kann der Compiler den Überlauf “benutzen”, um den Code abzukürzen. Wenn Sie wie unten mit U ändern, “funktioniert”.
inline bool divisible15(unsigned int x)
{
//286331153 = (2^32 - 1) / 15
//4008636143 = (2^32) - 286331153
return x * 4008636143u <= 286331153u;
}
testen mit:
#include <iostream>
inline bool divisible15a(unsigned int x)
{
//286331153 = (2^32 - 1) / 15
//4008636143 = (2^32) - 286331153
// return x * 4008636143 <= 286331153;
return x * 4008636143u <= 286331153;
}
inline bool divisible15b(unsigned int x)
{
//286331153 = (2^32 - 1) / 15
//4008636143 = (2^32) - 286331153
// return x * 4008636143 <= 286331153;
return x * 4008636143 <= 286331153;
}
int main()
{
for(unsigned int i = 0; i < 100; i++)
{
if (divisible15a(i))
{
std::cout << "a:" << i << std::endl;
}
if (divisible15b(i))
{
std::cout << "b:" << i << std::endl;
}
}
}
Ausgabe:
a:0
b:0
a:15
a:30
a:45
a:60
a:75
a:90
Code:
_Z12divisible15aj:
.LFB1192:
pushq %rbp
movq %rsp, %rbp
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
imull $-286331153, %eax, %eax
cmpl $286331153, %eax
setbe %al
popq %rbp
ret
_Z12divisible15bj:
.LFB1193:
pushq %rbp
movq %rsp, %rbp
movl %edi, -4(%rbp)
movl -4(%rbp), %edx
movl $4008636143, %eax
imulq %rdx, %rax
cmpq $286331153, %rax
setle %al
popq %rbp
ret
Also, ja, ich stimme der Analyse von Carl/Adam zu, dass es nicht in ein 32-Bit-Int passt, also zu befördert wird long
oder long long
.
Ist das schneller als
(x % 15) == 0
?– asveikau
9. September 2013 um 20:57 Uhr
Es zeigt sich mir nicht als undefiniertes Verhalten? Es ist jedoch wahrscheinlich ein Integer-Überlauf.
– PherricOxide
9. September 2013 um 20:58 Uhr
Tut
x * 4008636143
in ein int passen?– Andre
9. September 2013 um 20:59 Uhr
@millimoose Nun … das sind ohne Vorzeichen int. Das Überlaufverhalten wird festgelegt.
– Mystisch
9. September 2013 um 21:04 Uhr
Aus Gründen der Lesbarkeit denke ich, dass dies besser geschrieben ist als
return x * -(UINT_MAX / 15) <= (UINT_MAX / 15);
, was das ganze Problem schön vermeidet. Ich war verwirrt, als ich versuchte, es zu verstehen, erkannte, dass es so umgeschrieben werden konnte, und verstand dann, warum es funktionierte 🙂 (Bearbeiten: Dies nimmt, wie Ihr Code, offensichtlich einen bestimmten Wert für anUINT_MAX
.)– Benutzer743382
9. September 2013 um 22:38 Uhr