toge's diary

コンピュータ関連の趣味をつらつらと。

[C++] printfを使わずに実数を表示する 2

一時的なstd:stringの生成を抑えて、1つのstd::stringをぐりぐり動かす実装にしたら、やっとprintfは追い抜いた。template版で20%程度高速。

まだstd::stringのオーバーヘッドが残っているので、もう少し速くなるはず。

#include <iostream>
#include <string>
#include <cmath>

static
void
i2s(std::string& str, int value, int keta)
{
  std::string::iterator position = str.end();
  for (int index = 0; index < keta; ++index)
  {
    str.insert(position, 1, '0' + (value % 10));
    value /= 10;
  }
}

std::string
f2s(double value, int keta, int precision = 6, bool sign = false, char padding = ' ', char separate = '.')
{
  std::string result;
  result.reserve(keta);

  int part_int = std::floor(value);
  double part_float = value - part_int;
  int part_int_length = 0;

  for (int index = 1; index < part_int ; index *= 10)
  {
    part_int_length++;
  }

  int part_int_max;
  if (keta < precision)
  {
    part_int_max = 0;
  }
  else
  {
    part_int_max = keta - 1 - precision;
  }

  if (value < 0)
  {
    result = '-';
    part_int_max--;
  }
  else if (sign == true)
  {
    if (value < 0)
    {
      result = '-';
    }
    else
    {
      result = '+';
    }
    part_int_max--;
  }

  for (int index = 0; index < part_int_max - part_int_length; ++index)
  {
    result += padding;
  }

  i2s(result, part_int, part_int_length);
  result += separate;

  for (int index = 0; index < precision; index++)
  {
    part_float *= 10;
  }

  i2s(result, (int)part_float, precision);

  return result;
}

template <int v1>
class POWER10
{
public:
  enum
  {
    value = POWER10<v1 - 1>::value * 10
  };
};

template<>
class POWER10<1>
{
public:
  enum
  {
    value = 10
  };
};

template <int KETA, int PRESITION, bool SIGN = false, char PADDING = ' ', char SEPARATE = '.'>
class
FloatFormatterFix
{
private:
  static const int PART_INT_MAX = KETA - 1 - PRESITION;
  static const int PART_FLOAT_MULTI = POWER10<PRESITION>::value;

public:
  FloatFormatterFix()
  {}

public:
  std::string operator()(double value) const
  {
    std::string result;
    result.reserve(KETA);
    int part_int = floor(value);
    double part_float = value - part_int;

    int part_int_length = 0;
    for (int index = 1; index < part_int ; index *= 10)
    {
      part_int_length++;
    }

    if (value < 0)
    {
      result = '-';
      part_int_length++;
    }
    else if (SIGN == true)
    {
      if (value < 0)
      {
        result = '-';
      }
      else
      {
        result = '+';
      }
      part_int_length++;
    }

    for (int index = 0; index < PART_INT_MAX - part_int_length; ++index)
    {
      result += PADDING;
    }

    i2s(result, part_int, part_int_length);
    result += SEPARATE;
    i2s(result, (int)(part_float * PART_FLOAT_MULTI), PRESITION);

    return result;
  }
};

#include "sys/time.h"


int main()
{
  const int LOOP = 1000000;
  struct timeval start, end;

  gettimeofday(&start, NULL);
  printf("%ld.%06d\n", start.tv_sec, start.tv_usec);

  char buf[20];
  for (int index = 0; index < LOOP; ++index)
  {
    snprintf(buf, 20, "%10.8f", 13.5343);
  }
  gettimeofday(&end, NULL);
  printf("%d.%06d\n", end.tv_sec, end.tv_usec);

  printf("%s\n", buf);

  std::string a;

  gettimeofday(&start, NULL);
  printf("%ld.%06d\n", start.tv_sec, start.tv_usec);

  for (int index = 0; index < LOOP; ++index)
  {
    a = f2s(13.5343, 10,8);
  }

  gettimeofday(&end, NULL);
  printf("%d.%06d\n", end.tv_sec, end.tv_usec);

  printf("%s\n", a.c_str());

  gettimeofday(&start, NULL);
  printf("%ld.%06d\n", start.tv_sec, start.tv_usec);

  FloatFormatterFix<10, 8, false, ' ', ';'> formatter;
  for (int index = 0; index < LOOP; ++index)
  {
    a = formatter(13.5343);
  }

  gettimeofday(&end, NULL);
  printf("%d.%06d\n", end.tv_sec, end.tv_usec);

  printf("%s\n", a.c_str());

  return 0;
}