Skip to content

File floattostring.h

File List > code_source > templat > include > TempLat > util > floattostring.h

Go to the documentation of this file

#ifndef TEMPLAT_UTIL_FLOATTOSTRING_H
#define TEMPLAT_UTIL_FLOATTOSTRING_H

/* This file is part of TempLat, available at https://cosmolattice.github.io/templat .
   Copyright 2021-2026 The TempLat authors, see AUTHORS.md.
   Released under the MIT license, see LICENSE.md. */

// File info: Main contributor(s): Wessel Valkenburg, Year: 2019

#include "TempLat/util/stringtrimmer.h"

#include <cmath>
#include <cstring>
#include <string>

namespace TempLat
{
  class FloatToString
  {
  public:
    template <typename T>
      requires std::is_floating_point_v<T>
    static inline std::string format(T value)
    {
      char buffer[128];
      std::memset(buffer, 0, 128);
      if ((std::abs(value) > 1e3 || std::abs(value) < 1e-2) && std::abs(value) != 0) {
        snprintf(buffer, 128, "%11.2e", value);
        stripTrailingZeros(buffer, findThe_E(buffer), '*');
        squashChars(buffer, '*');
      } else {
        if (std::abs(value) < 1e-1)
          snprintf(buffer, 128, "%.3f", value);
        else if (std::abs(value) < 1)
          snprintf(buffer, 128, "%.2f", value);
        else
          snprintf(buffer, 128, "%.1f", value);
        stripTrailingZeros(buffer, buffer + 128, '\0');
      }
      return StringTrimmer::trimmed(buffer);
    }

    template <typename T>
      requires std::is_floating_point<T>::value
    static inline std::string format_more(T value, int ndig)
    {
      char buffer[128];
      std::memset(buffer, 0, 128);
      std::string fmat = "%11." + std::to_string(ndig) + "e";
      if ((std::abs(value) > 1e3 || std::abs(value) < 1e-2) && std::abs(value) != 0) {
        snprintf(buffer, 128, fmat.c_str(), value);
        stripTrailingZeros(buffer, findThe_E(buffer), '*');
        squashChars(buffer, '*');
      } else {
        if (std::abs(value) < 1e-1) {
          fmat = "%." + std::to_string(ndig) + "f";
          snprintf(buffer, 128, fmat.c_str(), value);
        } else if (std::abs(value) < 1) {
          fmat = "%." + std::to_string(ndig - 1) + "f";
          snprintf(buffer, 128, fmat.c_str(), value);
        } else {
          fmat = "%." + std::to_string(ndig - 2) + "f";
          snprintf(buffer, 128, fmat.c_str(), value);
        }
        stripTrailingZeros(buffer, buffer + 128, '\0');
      }
      return StringTrimmer::trimmed(buffer);
    }

  private:
    /* Put all member variables and private methods here. These may change arbitrarily. */

    static inline void stripTrailingZeros(char *begin, char *end, char replacement)
    {
      if (end <= begin) return;

      bool goOn = true;
      for (char *i = end - 1; i >= begin && goOn; --i) {
        switch (*i) {
        case '\0':
          break;
        case '0':
          /* cut trailing zeros. */
          *i = replacement;
          break;
        case '.':
          /* cut trailing dot. and stop. */
          *i = replacement;
          goOn = false;
          break;
        default:
          /* anything other than zero or dot? Done with the cutting. */
          goOn = false;
          break;
        }
      }
    }

    static inline char *findThe_E(char *string)
    {
      while (*string != '\0' && *string != 'e' && *string != 'E')
        ++string;
      return string;
    }

    static inline void squashChars(char *string, char replacement)
    {
      bool insideBatch = false;

      char *batchStart = string;

      char *iEnd = string;

      /* note that with this definition iEnd is a valid pointer, to the trailing '\0'-char. */
      while (*iEnd != '\0')
        ++iEnd;

      for (char *i = string; i < iEnd; ++i) {

        if (*i == replacement) {
          if (!insideBatch) batchStart = i;
          insideBatch = true;
        } else {
          if (insideBatch) {
            insideBatch = false;
            /* squash everything between batchStart and i  */
            ptrdiff_t batchSize = iEnd - i + 1; /* +1, because iEnd is a valid pointer to the trailing '\0'-char. */
            std::memmove(batchStart, i, batchSize);
          }
        }
      }
    }
  };
} // namespace TempLat

#endif