generate - params comments c#
Consigue el siguiente número doble más pequeño (2)
La página de Wikipedia sobre punto flotante de doble precisión está aquí: http://en.wikipedia.org/wiki/Double_precision_floating-point_format
Por diversión escribí un código para romper la representación binaria del formato double
, disminuye la mantisa y recompone el doble resultante. Debido al bit implícito en la mantisa, tenemos que buscarlo y modificar el exponente en consecuencia, y podría fallar cerca de los límites.
Aquí está el código:
public static double PrevDouble(double src)
{
// check for special values:
if (double.IsInfinity(src) || double.IsNaN(src))
return src;
if (src == 0)
return -double.MinValue;
// get bytes from double
byte[] srcbytes = System.BitConverter.GetBytes(src);
// extract components
byte sign = (byte)(srcbytes[7] & 0x80);
ulong exp = ((((ulong)srcbytes[7]) & 0x7F) << 4) + (((ulong)srcbytes[6] >> 4) & 0x0F);
ulong mant = ((ulong)1 << 52) | (((ulong)srcbytes[6] & 0x0F) << 48) | (((ulong)srcbytes[5]) << 40) | (((ulong)srcbytes[4]) << 32) | (((ulong)srcbytes[3]) << 24) | (((ulong)srcbytes[2]) << 16) | (((ulong)srcbytes[1]) << 8) | ((ulong)srcbytes[0]);
// decrement mantissa
--mant;
// check if implied bit has been removed and shift if so
if ((mant & ((ulong)1 << 52)) == 0)
{
mant <<= 1;
exp--;
}
// build byte representation of modified value
byte[] bytes = new byte[8];
bytes[7] = (byte)((ulong)sign | ((exp >> 4) & 0x7F));
bytes[6] = (byte)((((ulong)exp & 0x0F) << 4) | ((mant >> 48) & 0x0F));
bytes[5] = (byte)((mant >> 40) & 0xFF);
bytes[4] = (byte)((mant >> 32) & 0xFF);
bytes[3] = (byte)((mant >> 24) & 0xFF);
bytes[2] = (byte)((mant >> 16) & 0xFF);
bytes[1] = (byte)((mant >> 8) & 0xFF);
bytes[0] = (byte)(mant & 0xFF);
// convert back to double and return
double res = System.BitConverter.ToDouble(bytes, 0);
return res;
}
Todo lo cual le da un valor que es diferente del valor inicial por un cambio en el bit más bajo de la mantisa ... en teoría :)
Aquí hay una prueba:
public static Main(string[] args)
{
double test = 1.0/3;
double prev = PrevDouble(test);
Console.WriteLine("{0:r}, {1:r}, {2:r}", test, prev, test - prev);
}
Da los siguientes resultados en mi PC:
0.33333333333333331, 0.33333333333333326, 5.5511151231257827E-17
La diferencia está ahí, pero probablemente esté por debajo del umbral de redondeo. La expresión test == prev
evalúa como falso, y hay una diferencia real como se muestra arriba :)
Como parte de una prueba unitaria, necesito probar algunas condiciones de contorno. Un método acepta un argumento System.Double
.
¿Hay alguna manera de obtener el siguiente valor doble más pequeño ? (es decir, disminuir la mantisa por valor de 1 unidad)?
Consideré usar Double.Epsilon
pero esto no es confiable ya que solo es el delta más pequeño desde cero, y por lo tanto no funciona para valores más grandes (es decir, 9999999999 - Double.Epsilon == 9999999999
).
Entonces, ¿cuál es el algoritmo o código necesario para que:
NextSmallest(Double d) < d
... siempre es cierto.
Si sus números son finitos, puede usar un par de métodos convenientes en la clase BitConverter
:
long bits = BitConverter.DoubleToInt64Bits(value);
if (value > 0)
return BitConverter.Int64BitsToDouble(bits - 1);
else if (value < 0)
return BitConverter.Int64BitsToDouble(bits + 1);
else
return -double.Epsilon;
Los formatos IEEE-754 se diseñaron de modo que los bits que forman el exponente y la mantisa juntos formen un número entero que tiene el mismo orden que los números de punto flotante. Entonces, para obtener el número más pequeño más grande, puede restar uno de este número si el valor es positivo, y puede agregar uno si el valor es negativo.
La razón clave por la que esto funciona es que el bit inicial de la mantisa no se almacena. Si tu mantisa es todo ceros, entonces tu número es una potencia de dos. Si restas 1 de la combinación de exponente / mantisa, obtienes todos unos y tendrás que pedir prestado de los bits del exponente. En otras palabras: tienes que disminuir el exponente, que es exactamente lo que queremos.