Ir al contenido



Foto

Encontrar el signo de un double en C


  • Por favor identifícate para responder
8 respuestas en este tema

#1 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.691 mensajes
  • LocationMadrid - España

Escrito 17 octubre 2015 - 05:36

Para saber el signo de un double y obtener un entero +1 ó -1, podemos usar una función como esta, usando las utilidades matemáticas:
 

cpp
  1. #include <math.h>
  2. int Sign(double d)
  3. {
  4.   if(d==0) return 0;
  5. if(fabs(d)+d == 0) return - 1;
  6.   return 1;
  7. }

Pero si no queremos o no podemos usar math.h, tenemos esta otra forma a bajo nivel:
 

cpp
  1. int Sign(double d)
  2. {
  3. if(d==0) return 0;
  4. DWORD* R = (DWORD*)&d + 1;
  5. if(*R >> 31 & 1) return -1;
  6. return 1;
  7. }

Saludos.
  • 2

#2 BDWONG

BDWONG

    Member

  • Miembros
  • PipPip
  • 26 mensajes

Escrito 18 octubre 2015 - 08:02

Hola escanfandra no es lo mismo hacer esto o no entendi bien tu idea


cpp
  1. int sign2(double d){
  2. if(d==0)return 0;
  3. else if(d<0) return -1;
  4. else return 1;
  5. }


  • 0

#3 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.691 mensajes
  • LocationMadrid - España

Escrito 18 octubre 2015 - 09:40

Hola escanfandra no es lo mismo hacer esto o no entendi bien tu idea


cpp
  1. int sign2(double d){
  2. if(d==0)return 0;
  3. else if(d<0) return -1;
  4. else return 1;
  5. }


Si, entendiste bien la idea. Delphi tiene una función Sign de la que carece C, pero no Builder.
La verdadera idea del tema era usar la representación en bits a bajo nivel de un double para encontrar el bit que representa el signo desde una perspectiva de compilación a 32 bits pues en 64 hubiera sido más sencillo, a nivel bits.

Saludos.
  • 0

#4 Delphius

Delphius

    Advanced Member

  • Administrador
  • 5.983 mensajes
  • LocationArgentina

Escrito 19 octubre 2015 - 11:01

No recuerdo si la función Sign() en Delphi tiene una implementación a bajo nivel como la que describes. Lazarus tiene la implementación "tradicional" a base de ifs como la que expone BDWONG:


delphi
  1. function Sign(const AValue: Double): TValueSign;inline;
  2. begin
  3. If Avalue<0.0 then
  4. Result:=NegativeValue
  5. else If Avalue>0.0 then
  6. Result:=PositiveValue
  7. else
  8. Result:=ZeroValue;
  9. end;

Es curioso que lo implementen así. Creería que es muchísimo más rápido y apropiado ir por bajo nivel y leer el bit en cuestión.

 

Saludos,


  • 0

#5 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.691 mensajes
  • LocationMadrid - España

Escrito 19 octubre 2015 - 12:04

Esta rutina va a perder poco tiempo de cálculo. Podría pensarse que los saltos condicionales de ASM pueden sustituir la localización del bit de signo, eso sería cierto enteros pero no en double en un programa compliado para 32bits. En un programa compilado para 64 bits es más directo, puesto que el signo está en el bit más significativo y double es de longitud 64.

Hay que tener en cuenta el orden de significación de bits de un procesador, Intel los tiene invertidos y mi código está basado en eso. Si queremos que el código funcione para otro tipo de procesador, quizás la opción de Lazarus es lo mejor.

Considero que la forma más rápida de encontrar el signo en los procesadores que usamos Windows, es el bajo nivel descrito.


Saludos.
  • 1

#6 Delphius

Delphius

    Advanced Member

  • Administrador
  • 5.983 mensajes
  • LocationArgentina

Escrito 19 octubre 2015 - 02:01

Ummm. Yo que sepa el tipo double ya está definido para un tamaño de bits y formato particular y éste es independiente de la arquitectura es 32 o 64 bits. Es decir el signo estará en el mismo lugar... el problema pasa que en 32bits debe cuidarse de leer "la mitad" correcta.
 
Al menos Lazarus, para funciones como IsNan mediante cláusulas de compilación evalúa si es Big-endian y procede de una forma u otra:


pascal
  1. {$ifdef FPC_HAS_TYPE_DOUBLE}
  2. function IsNan(const d : Double): Boolean;
  3. var
  4. fraczero, expMaximal: boolean;
  5. begin
  6. {$if defined(FPC_BIG_ENDIAN) or defined(FPC_DOUBLE_HILO_SWAPPED)}
  7. expMaximal := ((TSplitDouble(d).cards[0] shr 20) and $7ff) = 2047;
  8. fraczero:= (TSplitDouble(d).cards[0] and $fffff = 0) and
  9. (TSplitDouble(d).cards[1] = 0);
  10. {$else FPC_BIG_ENDIAN}
  11. expMaximal := ((TSplitDouble(d).cards[1] shr 20) and $7ff) = 2047;
  12. fraczero := (TSplitDouble(d).cards[1] and $fffff = 0) and
  13. (TSplitDouble(d).cards[0] = 0);
  14. {$endif FPC_BIG_ENDIAN}
  15. Result:=expMaximal and not(fraczero);
  16. end;
  17. {$endif FPC_HAS_TYPE_DOUBLE}

Saludos,
  • 0

#7 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.691 mensajes
  • LocationMadrid - España

Escrito 19 octubre 2015 - 06:08

Ummm. Yo que sepa el tipo double ya está definido para un tamaño de bits y formato particular y éste es independiente de la arquitectura es 32 o 64 bits. Es decir el signo estará en el mismo lugar... el problema pasa que en 32bits debe cuidarse de leer "la mitad" correcta.

Ese es el tema, su estructura está bien definida pero sabes que los microprocesadores no interpretan el mismo orden de bits a bajo nivel. Es por eso, y dices bien, que debes encontrar la mitad correcta.

 

 

Saludos.


  • 0

#8 cram

cram

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 824 mensajes
  • LocationMisiones, Argentina

Escrito 19 octubre 2015 - 06:18

Para saber el signo de un double y obtener un entero +1 ó -1, podemos usar una función como esta, usando las utilidades matemáticas:
 


cpp
  1. #include <math.h>
  2. int Sign(double d)
  3. {
  4.   if(d==0) return 0;
  5. if(fabs(d)+d == 0) return - 1;
  6.   return 1;
  7. }

Pero si no queremos o no podemos usar math.h, tenemos esta otra forma a bajo nivel:
 

cpp
  1. int Sign(double d)
  2. {
  3. if(d==0) return 0;
  4. DWORD* R = (DWORD*)&d + 1;
  5. if(*R >> 31 & 1) return -1;
  6. return 1;
  7. }

Saludos.

 

 

Solo por curiosidad,

Luego de la pregunta sobre si es cero hay una salida del procedimiento.

¿cómo se maneja el bit de signo para el cero?. ¿es impredecible y por eso la consulta por si es cero va al principio?


  • 0

#9 Delphius

Delphius

    Advanced Member

  • Administrador
  • 5.983 mensajes
  • LocationArgentina

Escrito 19 octubre 2015 - 07:22

Solo por curiosidad,

Luego de la pregunta sobre si es cero hay una salida del procedimiento.

¿cómo se maneja el bit de signo para el cero?. ¿es impredecible y por eso la consulta por si es cero va al principio?

 

Según la norme IEEE el cero es un caso especial. Y tiene dos posibles valores: +0.0 y -0.0, que para la norma tiene un valor a nivel de bits conocido. Y como dice el compañero Escafandra, hay que tener cuidado si hay que leerlo según si es 32bits o 64bits.

Si tienes activada la palabra de control para detectar el caso puedes capturar e identificar que zero se ha dado. Por lo general los compiladores por defecto detectan el cero indistintamente y lo asumen como positivo y en la mayoría de los casos no hace falta evaluar esto. En la mayoría de los casos basta con emplear la función IsZero() que viene en la unidad Math que se encarga de aplicar un valor Absoluto y de esa forma el -0.0 se transforma en 0.0. Opcionalmente IsZero() puede recibir el valor de tolerancia en error absoluto para considerar cuando un número sea cero. Por defecto es 1E-12 para el tipo double, 1E-4 para single y para extended 1E-16:


delphi
  1. function IsZero(const A: Double; Epsilon: Double): Boolean;
  2. begin
  3. if (Epsilon=0) then
  4. Epsilon:=DZeroResolution;
  5. Result:=Abs(A)<=Epsilon;
  6. end;

Como puedes ver en el código no hace las cosas a bajo nivel. De esta forma evita lidiar con los problemas de arquitectura y deja que la magia del compilador haga el trabajo de interpretar el valor.

 

El sitio de Efg tiene un buen material para entender todo sobre los números especiales en Delphi. No tiene desperdicio.

 

Saludos,


  • 0