0

I want to keep 1 digit for my number.

For example:

3414.1 -> 3000
212 -> 200
12.1 -> 10
0.787 -> 0.7

I can come up with an idea of using std::string, that will transfer double into std::string, then transfer it back after handling.

but i think it will be slow, is there any good method i can use?

nick
  • 832
  • 3
  • 12
  • 4
    Does this answer your question? [C++ significant figures](https://stackoverflow.com/questions/4607473/c-significant-figures) – Passerby Jan 04 '22 at 06:54
  • 2
    Elaborate on your use cases (calculation, display ..etc) so this question could be specifically answered. It's too vague to answer at the moment. – Louis Go Jan 04 '22 at 07:13
  • If negative, compute "-your number" and deal with the sign appropriately. If >=1 truncate to an integral type (take care with overflow) and repeatedly divide by 10 and stop just before you reach 0. If [0, 1) repeatedly multiply by 10 until >=1 and truncate the result. – Bathsheba Jan 04 '22 at 08:04
  • Not `0.787 -> 0.8` ? Rounding down is a valid choice, as is rounding towards zero, but you probably should be explicit what you want. – MSalters Jan 04 '22 at 08:53

2 Answers2

1

You should find length of the number, for this we can use log10 function: ceil(log10(3414)) = 4, but we want most significant value then we use floor(log10(3414)) = 3, then we calculate like this: (3414 / 1000) * 1000 = 3000. Here complete solution:

#include <iostream>
#include <cmath>
template <typename T>
T f(T x) {
    if(x == 0) return 0;
    if(x < 0) return -f(-x);
    if(x < 1) {
        return f(x*10)/10.0;
    }
    auto t = pow(10, floor(log10(ceil(x))));
    return long(x/t) * t;
}

int main() {
    std::cout << "f(3414.1) -> " << f(3414.1) << '\n';
    std::cout << "f(212) -> " << f(212) << '\n';
    std::cout << "f(12.1) -> " << f(12.1) << '\n';
    std::cout << "f(0.787) -> " << f(0.787) << '\n';
    std::cout << "f(7.87) -> " << f(7.87) << '\n';
    std::cout << "f(70.87) -> " << f(70.87) << '\n';
    std::cout << "f(-0.787) -> " << f(-0.7) << '\n';
    std::cout << "f(-3414.1) -> " << f(-3414.1) << '\n';
    std::cout << "f(-212) -> " << f(-212) << '\n';
}

The output will be:

f(3414.1) -> 3000
f(212) -> 200
f(12.1) -> 10
f(0.787) -> 0.7
f(7.87) -> 7
f(70.87) -> 70
f(-0.787) -> -0.7
f(-3414.1) -> -3000
f(-212) -> -200
S4eed3sm
  • 1,398
  • 5
  • 20
  • Mathematically correct, but a bit of overhead (log10, pow). For performance (if needed, makes the code less concise), the pow^10 could be done with just the multiplication and a table of powers of 10 with the length as index. The index can be of course reached by a binary search, but better by extracting the binary exponent (e.g. https://stackoverflow.com/questions/15685181/how-to-get-the-sign-mantissa-and-exponent-of-a-floating-point-number ) of a normalized floating point number so that one final comparison to the exact value from the aforementioned table should be enough for base10 length. – Sebastian Jan 04 '22 at 09:23
1

Here is my version. If you care about performance you should benchmark. You didn't specify how to handle negative values so I assumed you "keep" the digit.

#include <concepts>
#include <cmath>

constexpr auto keep_msd(std::integral auto n)
{
    decltype(n) ten_pow = 1;

    while (n > 9 || n < -9)
    {
        n /= 10;
        ten_pow *= 10;
    }

    return n * ten_pow;
}

template <class F>
    requires std::floating_point<F>
constexpr F keep_msd(F f)
{
    if (!std::isfinite(f))
        return f;

    if (f == 0.0)
        return 0.0;

    if (f >= 1 || f <= -1)
        return static_cast<F>(keep_msd(static_cast<long long>(f)));

    F sig = f < 0 ? -1.0 : 1.0;
    f *= sig;

    F ten_pow = 1.0;
    
    while (f <= 1)
    {
        f *= static_cast<F>(10.0);
        ten_pow /= static_cast<F>(10.0);
    }

    return sig * static_cast<int>(f) * ten_pow;
}

Keep in mind that floating point math is broken so with that out of the way here is the test suite:

template <class F>
    requires std::floating_point<F>
constexpr bool are_almost_eq(F a, F b, F delta)
{
    return std::abs(a - b) <= delta;
}

static_assert(keep_msd(0) == 0);

static_assert(keep_msd(5) == 5);
static_assert(keep_msd(10) == 10);
static_assert(keep_msd(12) == 10);
static_assert(keep_msd(1'234) == 1'000);
static_assert(keep_msd(1'004) == 1'000);
static_assert(keep_msd(1'000) == 1'000);


static_assert(keep_msd(-5) == -5);
static_assert(keep_msd(-10) == -10);
static_assert(keep_msd(-12) == -10);
static_assert(keep_msd(-1'234) == -1'000);
static_assert(keep_msd(-1'004) == -1'000);
static_assert(keep_msd(-1'000) == -1'000);

static_assert(keep_msd(0.0) == 0.0);
static_assert(keep_msd(std::numeric_limits<double>::infinity()) ==
                std::numeric_limits<double>::infinity());
static_assert(keep_msd(-std::numeric_limits<double>::infinity()) ==
                -std::numeric_limits<double>::infinity());


static_assert(keep_msd(1.2) == 1.0);
static_assert(keep_msd(1.2) == 1.0);
static_assert(keep_msd(1.002) == 1.0);
static_assert(keep_msd(1'000.0004) == 1'000.0);
static_assert(keep_msd(1'002.3004) == 1'000.0);

static_assert(keep_msd(-1.2) == -1.0);
static_assert(keep_msd(-1.2) == -1.0);
static_assert(keep_msd(-1.002) == -1.0);
static_assert(keep_msd(-1'000.0004) == -1'000.0);
static_assert(keep_msd(-1'002.3004) == -1'000.0);

static_assert(are_almost_eq(keep_msd(0.1), 0.1, 0.000'000'1));
static_assert(are_almost_eq(keep_msd(0.3), 0.3, 0.000'000'1));
static_assert(are_almost_eq(keep_msd(0.123456), 0.1, 0.000'000'1));
static_assert(are_almost_eq(keep_msd(0.000'001), 0.000'001, 0.000'000'001));

static_assert(are_almost_eq(keep_msd(-0.1), -0.1, 0.000'000'1));
static_assert(are_almost_eq(keep_msd(-0.3), -0.3, 0.000'000'1));
static_assert(are_almost_eq(keep_msd(-0.123456), -0.1, 0.000'000'1));
static_assert(are_almost_eq(keep_msd(-0.000'001), -0.000'001, 0.000'000'001));

bolov
  • 72,283
  • 15
  • 145
  • 224