Me encontré con el código de alguien que parece creer que hay un problema al restar un entero sin signo de otro entero del mismo tipo cuando el resultado sería negativo. Entonces, ese código como este sería incorrecto incluso si funciona en la mayoría de las arquitecturas.
unsigned int To, Tf;
To = getcounter();
while (1) {
Tf = getcounter();
if ((Tf-To) >= TIME_LIMIT) {
break;
}
}
Esta es la única cita vagamente relevante del estándar C que pude encontrar.
Un cálculo que involucre operandos sin signo nunca puede desbordarse, porque un resultado que no puede ser representado por el tipo entero sin signo resultante se reduce módulo al número que es uno mayor que el valor más grande que puede ser representado por el tipo resultante.
Supongo que se podría tomar esa cita en el sentido de que cuando el operando derecho es más grande, la operación se ajusta para que sea significativa en el contexto de números truncados de módulo.
es decir
0x0000 - 0x0001 == 0x 1 0000 - 0x0001 == 0xFFFF
en lugar de utilizar la semántica firmada dependiente de la implementación:
0x0000 - 0x0001 == (sin firmar) (0 + -1) == (0xFFFF pero también 0xFFFE o 0x8001)
¿Cuál o qué interpretación es la correcta? ¿Está definido en absoluto?
Respuestas:
El resultado de una resta que genera un número negativo en un tipo sin signo está bien definido:
Como puede ver,
(unsigned)0 - (unsigned)1
es igual a -1 módulo UINT_MAX + 1, o en otras palabras, UINT_MAX.Tenga en cuenta que, aunque dice "Un cálculo que implica operandos sin firmar nunca puede desbordarse", lo que podría llevarlo a creer que solo se aplica para exceder el límite superior, esto se presenta como una motivación para la parte vinculante real de la oración: "a el resultado que no puede ser representado por el tipo entero sin signo resultante se reduce módulo el número que es uno mayor que el valor más grande que puede ser representado por el tipo resultante. " Esta frase no se limita al desbordamiento del límite superior del tipo y se aplica igualmente a valores demasiado bajos para ser representados.
fuente
uint
siempre tenía la intención de representar a la matemática del anillo de los enteros0
a travésUINT_MAX
, con las operaciones de suma y módulo de multiplicaciónUINT_MAX+1
, y no porque de un desbordamiento. Sin embargo, plantea la pregunta de por qué, si los anillos son un tipo de datos tan fundamental, el lenguaje no ofrece un soporte más general para anillos de otros tamaños.Cuando trabaja con tipos sin firmar , se lleva a cabo la aritmética modular (también conocida como comportamiento "envolvente" ). Para comprender esta aritmética modular , solo eche un vistazo a estos relojes:
9 + 4 = 1 ( 13 mod 12 ), entonces en la otra dirección es: 1 - 4 = 9 ( -3 mod 12 ). El mismo principio se aplica al trabajar con tipos sin firmar. Si el tipo de resultado es
unsigned
, entonces se lleva a cabo la aritmética modular.Ahora observe las siguientes operaciones que almacenan el resultado como un
unsigned int
:Cuando desee asegurarse de que el resultado sea
signed
, guárdelo ensigned
variable o conviértalo ensigned
. Cuando desee obtener la diferencia entre números y asegurarse de que no se aplicará la aritmética modular, debe considerar usar laabs()
función definida enstdlib.h
:Tenga mucho cuidado, especialmente al escribir las condiciones, porque:
pero
fuente
int d = abs(five - seven);
no es buena. Primerofive - seven
se calcula: la promoción deja los tipos de operandos comounsigned int
, el resultado se calcula en módulo(UINT_MAX+1)
y se evalúa comoUINT_MAX-1
. Entonces este valor es el parámetro real deabs
, lo cual es una mala noticia.abs(int)
provoca un comportamiento indefinido que pasa el argumento, ya que no está dentro del rango, yabs(long long)
probablemente puede contener el valor, pero el comportamiento indefinido ocurre cuando el valor de retorno es forzadoint
a inicializarsed
.operator T()
. La suma en las dos expresiones que estamos discutiendo se realiza en tipounsigned int
, basado en los tipos de operandos. El resultado de la adición esunsigned int
. Luego, ese resultado se convierte implícitamente al tipo requerido en el contexto, una conversión que falla porque el valor no se puede representar en el nuevo tipo.double x = 2/3;
vsdouble y = 2.0/3;
Bueno, la primera interpretación es correcta. Sin embargo, su razonamiento sobre la "semántica firmada" en este contexto es incorrecto.
Nuevamente, su primera interpretación es correcta. La aritmética sin signo sigue las reglas de la aritmética de módulo, lo que significa que se
0x0000 - 0x0001
evalúa0xFFFF
para tipos sin signo de 32 bits.Sin embargo, la segunda interpretación (la basada en "semántica con signo") también es necesaria para producir el mismo resultado. Es decir, incluso si evalúa
0 - 1
en el dominio de tipo firmado y obtiene-1
como resultado intermedio, esto-1
aún es necesario para producirlo0xFFFF
cuando más tarde se convierta a tipo sin firmar. Incluso si alguna plataforma utiliza una representación exótica para enteros con signo (complemento de 1, magnitud con signo), esta plataforma aún debe aplicar reglas de aritmética de módulo al convertir valores enteros con signo en valores sin signo.Por ejemplo, esta evaluación
todavía está garantizado para producir
UINT_MAX
enc
, incluso si la plataforma está utilizando una representación exótica para enteros con signo.fuente
Con números sin signo de tipo
unsigned int
o mayor, en ausencia de conversiones de tipo,a-b
se define como el resultado del número sin signo que, cuando se agregab
, dará como resultadoa
. La conversión de un número negativo a sin signo se define como el resultado del número que, cuando se agrega al número original con signo invertido, dará como resultado cero (por lo tanto, convertir -5 a sin signo arrojará un valor que, cuando se suma a 5, dará como resultado cero) .Tenga en cuenta que los números sin signo más pequeños que
unsigned int
pueden ser promocionados a tipografíaint
antes de la resta, el comportamiento dea-b
dependerá del tamaño deint
.fuente