Source

Magnum / source / libs / magnum_core / source / magnum / object / Float_writer.h

Full commit
/*
  Magnum
  Copyright (C) 2002-2012 Kaya Kupferschmidt. All Rights Reserved.

  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely, under the terms in LICENSE.TXT.

  Kaya Kupferschmidt  (k.kupferschmidt@dimajix.de)
*/

#ifndef __MAGNUM_OBJECT_FLOATWRITER_H
#define __MAGNUM_OBJECT_FLOATWRITER_H

#ifndef __MAGNUM_UTIL_FLOAT_H
#include "magnum/object/Exception.h"
#include "magnum/object/traits/IteratorTraits.h"
#include "magnum/util/minmax.h"
#endif

#include <stdlib.h> // for ecvt


namespace magnum { namespace object { namespace impl {
using magnum::types::String;
using magnum::types::Character;
using magnum::types::traits::CharacterTraits;
using magnum::types::traits::IteratorTraits;
using magnum::types::traits::StringTraits;
using magnum::util::min;
using magnum::util::max;


class FloatWriterUtils {
public:
    static int roundBuffer(char* buffer,unsigned int precision)
    {
        // Check for empty buffer
        if (*buffer == 0)
            return 0;

        char *end = buffer+precision;

        // Check if buffer is shorter than precision
        char *b = buffer;
        for (; b != end; ++b) {
            if (*b == 0) {
                --b;
                return 0;
            }
        }

        // Round up backwards
        if (*end >= '5') {
            end--;
            while (true) {
                char c = *end;
                if (c < '9') {
                    *end++ = c + 1;
                    *end = 0;
                    return 0;
                }
                else if (end == buffer) {
                    *end++ = '1';
                    *end = 0;
                    return 1;
                }
                end--;
            }
        }

        // Write terminator
        *end = 0;

        // Clean up trailing zeros
        b = end - 1;
        if (*b == '0') {
            do {
                if (*b != '0')
                    break;
                *b-- = 0;
            } while (b != buffer);
            return 0;
        }

        return 0;
    }
    static void removeZeros(char* str, unsigned int precision)
    {
        for (char* ptr = str+precision-1; ptr != str; --ptr) {
            char c = *ptr;
            if (c != '0')
                break;
            *ptr = 0;
        }
    }
#if defined(HAVE_ECVT_S)
    static const char* float2buf(char* str, size_t bufsize, double value, int& sign, int& prept, int& exp, int precision)
#else
    static const char* float2buf(double value, int& sign, int& prept, int& exp, int precision)
#endif
    {
        int dcpt = 0;
#if defined(HAVE_ECVT_S)
        _ecvt_s(str, bufsize, value, 32, &dcpt, &sign);
#else
        char* str = ecvt(value, 32, &dcpt, &sign);
#endif

        // Check if we need to do some rounding
        dcpt += roundBuffer(str, precision);

        // Remove trailing zeros
        //removeZeros(str, precision);

        // Calculate location of decimal point and exponent
        int digits = dcpt - 1;
        if (dcpt < 0) {
            exp = digits + (-digits % 3);
        }
        else {
            exp = digits - (digits % 3);
        }
        prept = dcpt - exp;

        return str;
    }
};


/*--------------------------------------------------------------------------*/
/**
 */
template<class NumberT>
class FloatWriter : public FloatWriterUtils {
public:
    typedef NumberT ValueType;

public:
#if 1
    /*--------------------------------------------------------------------------*/
    /**
     */
    template<class OutputIterT>
    static OutputIterT write(OutputIterT out, ValueType value, int precision)
    {
        // Very special case
        if (Limits<ValueType>::isInfinity(value)) {
            if (value < 0) {
                return writeString(out, "-INF");
            } else {
                return writeString(out, "+INF");
            }
        }
        if (Limits<ValueType>::isNaN(value)) {
            return writeString(out, "NaN");
        }

        int dcpt = 0;
        int sign = 0;
#if defined(HAVE_ECVT_S)
        char buffer[_CVTBUFSIZE];
        _ecvt_s(buffer, sizeof(buffer), value, Limits<ValueType>::decimals + 1, &dcpt, &sign);
#else
        char* buffer = ecvt(value, Limits<ValueType>::decimals + 1, &dcpt, &sign);
#endif
        char* str = buffer;
        int digits = (int)StringTraits<const char*>::getLength(str);

        // Create result string
        if (sign)
            *out++ = '-';

        // Check if result is too small to be represented
        if (-dcpt >= precision) {
            *out++ = '0';
            *out++ = '.';
            *out++ = '0';
            return out;
        }

        // Check if we need to do some rounding
        if (dcpt + precision < digits) {
            bool overflow = roundBuffer(str, max(0,dcpt + precision)) > 0;
            if (overflow) {
                dcpt--;
                if (dcpt < 0) {
                    *out++ = '0';
                    *out++ = '.';
                    while (dcpt++ < 0)
                        *out++ = '0';
                    *out++ = '1';
                }
                else {
                    *out++ = '1';
                    while (dcpt-- > 0) {
                        *out++ = '0';
                    }
                    *out++ = '.';
                    *out++ = '0';
                }
                return out;
            }
        }

        // Remove trailing zeros
        if (digits > dcpt) {
            removeZeros(str + dcpt, min(precision,digits - dcpt));
        }

        // Print out mantissa up to decimal point
        if (dcpt <= 0) {
            *out++ = '0';
            *out++ = '.';
            while (dcpt++ < 0)
                *out++ = '0';
        }
        else {
            // Treat first character such that no thousands separator will be printed
            if (dcpt-- > 0) {
                if (*str != 0)
                    *out++ = *str++;
                else
                    *out++ = '0';
            }

            // Print remaining characters
            while (*str != 0 && dcpt-- > 0) {
                *out++ = *str++;
            }
            while (dcpt-- > 0) {
                *out++ = '0';
            }

            // Add decimal point
            *out++ = '.';
        }

        // print out mantissa after decimal point
        if (*str != 0) {
            do {
                *out++ = *str++;
            } while (*str != 0);
        }
        else {
            *out++ = '0';
        }

        return out;
    }
#else
    /*--------------------------------------------------------------------------*/
    /**
     */
    template<class OutputIterT>
    static OutputIterT write(OutputIterT out, ValueType value, int maxPrecision)
    {
        // Very special case
        if (value == ValueType(0)) {
            return writeString(out, "0.0");
        }
        if (value == -ValueType(0)) {
            return writeString(out, "-0.0");
        }
        if (Limits<ValueType>::isInfinity(value)) {
            if (value < 0) {
                return writeString(out, "-INF");
            } else {
                return writeString(out, "+INF");
            }
        }
        if (Limits<ValueType>::isNaN(value)) {
            return writeString(out, "NaN");
        }

        int exp = 0;
        int sign = 0;
        int prept = 0;
#if defined(HAVE_ECVT_S)
        char buffer[_CVTBUFSIZE];
        const char* str = float2buf(buffer, sizeof(buffer), value, sign, prept, exp, maxPrecision);
#else
        const char* str = float2buf(value, sign, prept, exp, maxPrecision);
#endif

        // Create result string
        if (sign)
            *out++ = '-';

        // Print out mantissa up to decimal point
        if (prept <= 0) {
            *out++ = '0';
            *out++ = '.';
            while (prept++ < 0) {
                *out++ = '0';
            }
        }
        else {
            while (*str != 0 && prept-- > 0)
                *out++ = *str++;
            while (prept-- > 0)
                *out++ = '0';
            *out++ = '.';
        }

        // print out mantissa after decimal point
        if (*str != 0) {
            do {
                *out++ = *str++;
            } while (*str != 0);
        }
        else {
            *out++ = '0';
        }

        // Add exponent
        if (exp != 0) {
            out = addExponent(out, exp);
        }

        return out;
    }
#endif

private:
    template<class OutputIterT>
    static OutputIterT addExponent(OutputIterT out, int exp)
    {
        // Add exponent marker plus sign
        if (exp < 0) {
            *out++ = 'E';
            *out++ = '-';
            exp = -exp;
        }
        else {
            *out++ = 'E';
        }

        // Create exponent string in reverse order
        char buffer[20];
        char* ptr = buffer;
        do {
            unsigned int digval = (unsigned int) (exp % 10);
            exp /= 10;

            // Convert to ascii and store
            *ptr++ = (char)(digval + '0');
        } while (exp > 0);

        // Reverse string
        while (ptr != buffer) {
            *out++ = *--ptr;
        }

        return out;
    }

    template<class OutputIterT>
    static OutputIterT writeString(OutputIterT out, const char* src)
    {
        while (*src != 0) {
            *out++ = *src++;
        }
        return out;
    }
};


} } }

#endif