繁体   English   中英

如何使用前导零和四舍五入小数格式化双精度数

[英]How to format a double with leading zeroes and rounded decimals

是否有一种优雅的方式将 double 格式化为小数点前具有固定数量数字的字符串(用前导零填充),但只显示需要的小数且具有最大精度?

例如,前面固定 3 位数字,小数点后面最多 2 位数字:

// desired:
1.00000 -> "001"
1.10000 -> "001.1"
1.12345 -> "001.12"

// not:
1.10000 -> "0001.1" // too many leading zeroes
1.12345 -> "1.1235" // too few leading zeroes, too many digits after the decimal point

附加约束:

  • 小数需要实际四舍五入,而不是用空格填充
  • 负数不是问题,在此之前它们会被过滤掉
  • 输入在 [0.0, 180.0] 范围内

我们研究了 printf、stringstream、boost::format 和fmtlib ,但它们似乎都没有提供对小数点数字数量的特定控制。 控制这一点的标准方法是调整字段宽度和精度,但这似乎不能提供我们需要的粒度。

到目前为止,我们发现的最“优雅”的解决方案如下(其中123.1f是输入值):

boost::trim_right_copy_if(fmt::format("{:06.2f}", 123.1f), boost::is_any_of("0"))

但我不禁认为必须有一个更优雅/更强大的解决方案。


对于上下文,我们有一个显示纬度/经度坐标的 GUI。 我们的客户要求我们用前导零填充,但尽可能减少数字。 这是在减少不必要的信息和尽可能避免混淆之间的折衷。 例如:

W135°2'2.3344" -> W135°02'02.33"
W135°22.3344"  -> W135°00'22.33"
W135°2'3"      -> W135°02'03"
W135°22'2.999" -> W135°22'03"
W1°35"         -> W001°00'35"
W1°35'         -> W001°35'00"

这个怎么样:

#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>

void
output(double d)
{
    std::stringstream pre;
    pre << static_cast<long int>(d);

    std::stringstream post;
    post << d-static_cast<long int>(d);

    int pre_digits = pre.str().length();
    int post_digits = post.str().length() - pre_digits;
    int width = pre_digits + post_digits + 2;

    if (post_digits > 2) {
        post_digits = 2;
        width = pre_digits + post_digits + 3;
    }

    std::cout << std::setfill('0')
            << std::setprecision(pre_digits + post_digits)
            << std::setw(width)
            << d
            << '\n';
}

int main()
{
    output(1.00000);
    output(1.10000);
    output(1.12345);

    return 0;
}

结果是:

001
001.1
001.12

更新:进行了一些编辑以确保输出与您要查找的相同。

通过分别格式化整数和小数部分,您可以使用 {fmt} 或其他库轻松完成:

#include <cmath>
#include <fmt/core.h>

std::string double_to_string(double value) {
  double integral = 0;
  double fractional = std::modf(value, &integral);
  return fmt::format("{:03.0f}", integral) +
         fmt::format("{:.2g}", fractional).substr(1);
}

double_to_string(1.00000); // -> "001"
double_to_string(1.10000); // -> "001.1"
double_to_string(1.12345); // -> "001.12"

这可能需要微调以支持负值。

您也可以使用fmt::memory_bufferfmt::format_to来避免字符串分配。

您的解决方案也非常强大,因为fmt::format的输出是稳定的,不受语言环境的影响。

我最终使用了一次打印整个坐标的解决方案,因为我有大量的舍入误差分别处理度/分/秒(例如,2.0° 被打印为 1°60')。 它的灵感来自 vitaut 将整数和小数部分格式化为单独字符串的解决方案:

// value is the input in decimal degrees (e.g. 77° 20' = 77.3333333)
// deg_width controls if degrees are printed with 2 (latitude) or 3 (longitude) digits
std::string formatDMS (double value, size_t deg_width) 
{
    std::string res;
    static const int SECONDS_DECIMAL_PLACES = 2; // amount of decimals to print seconds with

    // Convert everything to int, to get rid of pesky floating-point errors
    static const int ONE_SECOND = std::pow (10, SECONDS_DECIMAL_PLACES);    // e.g. 1 second = 100 * 0.01 (seconds with 2 decimals)
    static const int ONE_MINUTE = 60 * ONE_SECOND;  // e.g. 1 minute = 6000 * 0.01 (seconds with 2 decimals)
    static const int ONE_DEGREE = 60 * ONE_MINUTE;  // e.g. 1 minute = 360000 * 0.01 (seconds with 2 decimals)

    const int value_incs = std::lround (value * ONE_DEGREE);
    const int degrees = value_incs / ONE_DEGREE;
    const int deg_rem = value_incs % ONE_DEGREE;
    const int minutes = deg_rem / ONE_MINUTE;
    const int min_rem = deg_rem % ONE_MINUTE;
    const int seconds = min_rem / ONE_SECOND;
    const int sec_rem = min_rem % ONE_SECOND;
    const double decimals = static_cast<double>(sec_rem) / ONE_SECOND;
    const auto decimals_string =
    fmt::format ("{:.{}g}", decimals, SECONDS_DECIMAL_PLACES).substr (1);

    const auto fmtstring = "{:0{}d}° {:02d}\' {:02d}{}\"";
    res += fmt::format (fmtstring, degrees, deg_width, minutes, seconds, decimals_string);
    return res;
}

formatDMS(77.0, 2);         // 77°00'00"
formatDMS(77.033333333, 2); // 77°02'00"
formatDMS(77.049722222, 2); // 77°02'59"
formatDMS(77.049888889, 2); // 77°02'59.6"
formatDMS(77.999999999, 2); // 78°00'00"
formatDMS(7.0, 3);          // 007°00'00"
formatDMS(7.2, 3);          // 007°12'00"
formatDMS(7.203333333, 3);  // 007°12'12"
formatDMS(7.203366667, 3);  // 007°12'12.12"
formatDMS(7.999999999, 3);  // 008°00'00"

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM