¿Por qué “dtoa.c” contiene tanto código?

9 minutos de lectura

avatar de usuario
CaosPandion

Seré el primero en admitir que mi conocimiento general de la programación de bajo nivel es un poco escaso. Entiendo muchos de los conceptos básicos, pero no los uso con regularidad. Dicho esto, estaba absolutamente asombrado de la cantidad de código que se necesitaba para dtoa.c.

Durante los últimos dos meses, he estado trabajando en una implementación de ECMAScript en C# y me he estado demorando en llenar los agujeros en mi motor. Anoche comencé a trabajar en Número.prototipo.aCadena que se describe en la sección 15.7.4.2 de El Especificación ECMAScript (pdf). En la sección 9.8.1la NOTA 3 ofrece un enlace a dtoa.c pero estaba buscando un desafío, así que esperé para verlo. Lo siguiente es lo que se me ocurrió.

private IDynamic ToString(Engine engine, Args args)
{
    var thisBinding = engine.Context.ThisBinding;
    if (!(thisBinding is NumberObject) && !(thisBinding is NumberPrimitive))
    {
        throw RuntimeError.TypeError("The current 'this' must be a number or a number object.");
    }

    var num = thisBinding.ToNumberPrimitive();

    if (double.IsNaN(num))
    {
        return new StringPrimitive("NaN");
    }
    else if (double.IsPositiveInfinity(num))
    {
        return new StringPrimitive("Infinity");
    }
    else if (double.IsNegativeInfinity(num))
    {
        return new StringPrimitive("-Infinity");
    }

    var radix = !args[0].IsUndefined ? args[0].ToNumberPrimitive().Value : 10D;

    if (radix < 2D || radix > 36D)
    {
        throw RuntimeError.RangeError("The parameter [radix] must be between 2 and 36.");
    }
    else if (radix == 10D)
    {
        return num.ToStringPrimitive();
    }

    var sb = new StringBuilder();
    var isNegative = false;

    if (num < 0D)
    {
        isNegative = true;
        num = -num;
    }

    var integralPart = Math.Truncate(num);
    var decimalPart = (double)((decimal)num.Value - (decimal)integralPart);
    var radixChars = RadixMap.GetArray((int)radix);

    if (integralPart == 0D)
    {
        sb.Append('0');
    }
    else
    {
        var integralTemp = integralPart;
        while (integralTemp > 0)
        {
            sb.Append(radixChars[(int)(integralTemp % radix)]);
            integralTemp = Math.Truncate(integralTemp / radix);
        }
    }

    var count = sb.Length - 1;
    for (int i = 0; i < count; i++)
    {
        var k = count - i;
        var swap = sb[i];
        sb[i] = sb[k];
        sb[k] = swap;
    }

    if (isNegative)
    {
        sb.Insert(0, '-');
    }

    if (decimalPart == 0D)
    {
        return new StringPrimitive(sb.ToString());
    }

    var runningValue = 0D;
    var decimalIndex = 1D;
    var decimalTemp = decimalPart;

    sb.Append('.');
    while (decimalIndex < 100 && decimalPart - runningValue > 1.0e-50)
    {
        var result = decimalTemp * radix;
        var integralResult = Math.Truncate(result);
        runningValue += integralResult / Math.Pow(radix, decimalIndex++);
        decimalTemp = result - integralResult;
        sb.Append(radixChars[(int)integralResult]);
    }

    return new StringPrimitive(sb.ToString());
}

¿Alguien con más experiencia en programación de bajo nivel puede explicar por qué? dtoa.c tiene aproximadamente 40 veces más código? Simplemente no puedo imaginar que C# sea mucho más productivo.

  • ¿Por qué no puedes imaginarlo? Ese tipo de diferencial es exactamente la razón por la cual los lenguajes como C# son tan populares.

    luego

    3 de julio de 2010 a las 22:39

  • @Neil: supongo que no es tan difícil de imaginar. Debería haberme dado cuenta de cuánto trabajo se necesita para hacer que el código nativo sea multiplataforma.

    – ChaosPandión

    3 de julio de 2010 a las 22:45


  • ¿Has probado este código con entradas negativas?

    –Mark Dickinson

    4 de julio de 2010 a las 8:37

  • @Mark – Bueno, maldita sea, hubo un error con números negativos. Buena atrapada.

    – ChaosPandión

    4 de julio de 2010 a las 16:19

  • yo tambien sospecho que (1.3333333333333333).toString(3) no da 1.1 con su implementación…

    – algo

    4 de julio de 2010 a las 17:19

avatar de usuario
rick regan

dtoa.c contiene dos funciones principales: dtoa(), que convierte un doble en cadena, y strtod(), que convierte una cadena en doble. También contiene muchas funciones de soporte, la mayoría de las cuales son para su propia implementación de aritmética de precisión arbitraria. El reclamo a la fama de dtoa.c es hacer estas conversiones correctas, y eso solo se puede hacer, en general, con aritmética de precisión arbitraria. También tiene código para redondear conversiones correctamente en cuatro modos de redondeo diferentes.

Su código solo intenta implementar el equivalente de dtoa(), y dado que usa punto flotante para realizar sus conversiones, no siempre las hará bien. (Actualización: ver mi artículo http://www.exploringbinary.com/quick-and-dirty-floating-point-to-decimal-conversion/ para detalles.)

(He escrito mucho sobre esto en mi blog, http://www.exploringbinary.com/ . Seis de mis últimos siete artículos han sido solo sobre conversiones de strtod(). Léalos para ver lo complicado que es hacer conversiones redondeadas correctamente).

  • Muy interesante, tengo que leer un poco y definitivamente tendré que explorar este código con mayor detalle.

    – ChaosPandión

    4 de julio de 2010 a las 1:51


  • Realmente no es difícil creer que extrañaría la conversión “en ambos sentidos” que se ve en este archivo. El código es realmente difícil de leer para mí.

    – ChaosPandión

    4 de julio de 2010 a las 1:54


  • +1. Sin mencionar que Gay’s dtoa (que es la más pequeña de las dos piezas de funcionalidad en dtoa.c) también permite al usuario especificar cuántos dígitos de salida y qué método de redondeo usar. Dicho sin rodeos, el código C# en la pregunta hace una pequeña fracción de lo que hace dtoa.c, y lo hace de manera imprecisa, lenta y con errores (pruébelo con números negativos, por ejemplo).

    –Mark Dickinson

    4 de julio de 2010 a las 8:42


  • @Chaos: la base de su pregunta es básicamente; “Mi código hace todo lo que hace dtoa, entonces, ¿por qué dtoa es tan grande?” Eso es incorrecto, su código no hace en absoluto lo que hace dtoa, así que no se sorprenda cuando la gente lo llame.

    – Ed S.

    18 de agosto de 2010 a las 0:40

  • Wow, DEMASIADA conversación aquí. Deberían haber hecho esto en el chat.

    – unixman83

    13 de febrero de 2012 a las 10:16

avatar de usuario
Mateo Slattery

Productor bien Los resultados de las conversiones entre representaciones decimales y binarias de punto flotante es un problema bastante difícil.

La principal fuente de dificultad es que muchas fracciones decimales, incluso las simples, no se pueden precisamente expresado usando punto flotante binario — por ejemplo, 0.5 puede (obviamente), pero 0.1 no poder. Y, yendo al revés (de binario a decimal), generalmente no desea obtener un resultado absolutamente preciso (por ejemplo, el valor decimal exacto del número más cercano a 0.1 que se puede representar en un IEEE-754 compatible double es en realidad
0.1000000000000000055511151231257827021181583404541015625) por lo que normalmente desea un poco de redondeo.

Entonces, la conversión a menudo implica una aproximación. Las buenas rutinas de conversión garantizan producir el más cercano aproximación posible dentro de restricciones particulares (tamaño de palabra o número de dígitos). Aquí es donde viene la mayor parte de la complejidad.

Eche un vistazo al documento citado en el comentario en la parte superior de la dtoa.c implementación, Clinger Cómo leer números de coma flotante con precisión, para tener una idea del problema; y quizás el artículo de David M. Gay (el autor), Conversiones binario-decimal y decimal-binario correctamente redondeadas.

(También, de manera más general: Lo que todo informático debe saber sobre la aritmética de punto flotante.)

  • Soy muy consciente de los desafíos que representa el punto flotante. Mi pregunta tiene más que ver con la diferencia en la cantidad de código. Cuando estaba probando mi ejemplo con Firefox, mi código produjo resultados idénticos. Firefox usa dtoa.c.

    – ChaosPandión

    4 de julio de 2010 a las 0:03

En base a un vistazo rápido, una buena cantidad de la versión C se ocupa de múltiples plataformas y, como parece, este archivo está destinado a ser genéricamente utilizable en compiladores (C y C ++), bits, implementaciones de coma flotante y plataformas. ; con toneladas de #define configurabilidad.

  • Estoy de acuerdo. También eché un breve vistazo al código, me sentí harto del trabajo que representa para una operación tan simple. Con .NET veo afortunadamente atrás estos tiempos para la programación de aplicaciones.

    – jdehaan

    3 de julio de 2010 a las 22:45

  • ¿No sería más fácil tener un archivo de archivos c con cada uno compatible con una plataforma separada? leyendo todos esos #ifdef los bloques me dieron un aneurisma cerebral…

    – ChaosPandión

    3 de julio de 2010 a las 22:49


  • Sí, el problema no son los idiomas, es solo un diseño incómodo en general. El mismo efecto podría lograrse con una mejor modularidad.

    – Rueda dentada

    3 de julio de 2010 a las 23:15


  • @ChaosPandion: depende de si desea que la configuración específica de su plataforma se realice en un archivo de encabezado C (estableciendo las definiciones) o en un archivo MAKE (seleccionando el archivo .c correcto para construir). Además, si hay suficientes definiciones para darle un aneurisma, podrían ser 2^archivos c distintos de aneurisma si se multiplicaran todas las posibilidades. En la práctica, al escribir este tipo de cosas ultraportátiles, generalmente desea una pequeña cantidad de archivos fuente (en este caso, 1, pero a veces algunos más) impulsados ​​por una gran cantidad de opciones de configuración, en lugar de al revés.

    –Steve Jessop

    4 de julio de 2010 a las 1:01


  • … y la razón por la que es posible que desee escribir el código para admitir plataformas en función de su comportamiento/propiedades, en lugar de escribir exactamente una implementación dtoa para cada plataforma, es para que pueda migrar a nuevas plataformas simplemente escribiendo un archivo de encabezado con la combinación correcta de define, en lugar de volver a escribir todo stdlib cada vez. Una implementación configurable masivamente como esta no siempre es la mejor, pero puede que no sea tan mala como parece cuando considera la alternativa.

    –Steve Jessop

    4 de julio de 2010 a las 1:03


También creo que el código en dtoa.c podría ser más eficiente (independientemente del idioma). Por ejemplo, parece estar jugando un poco, lo que en manos de un experto a menudo significa velocidad. Supongo que simplemente usa un algoritmo menos intuitivo por razones de velocidad.

Respuesta corta: porque dtoa.c obras.

Esta es exactamente la diferencia entre un producto bien depurado y un prototipo NIH.

  • “No inventado aquí” como en “código ad-hok personalizado, como alternativa al código popular de terceros”

    – Pavel Radzivilovsky

    4 de julio de 2010 a las 19:52

  • Quiero decir que tienes razón, pero no explica por qué hay tanto código. Quién sabe, tal vez alguien más escribió una implementación que convirtió correctamente con la mitad de código y el doble de funcionalidad.

    – ChaosPandión

    4 de julio de 2010 a las 19:57

  • “No inventado aquí” como en “código ad-hok personalizado, como alternativa al código popular de terceros”

    – Pavel Radzivilovsky

    4 de julio de 2010 a las 19:52

  • Quiero decir que tienes razón, pero no explica por qué hay tanto código. Quién sabe, tal vez alguien más escribió una implementación que convirtió correctamente con la mitad de código y el doble de funcionalidad.

    – ChaosPandión

    4 de julio de 2010 a las 19:57

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad