5

I have the following list of numbers, they are sent one by one to a function.

96.487587
96.561569
97.893280
97.967270
98.041245
98.115227
98.855072
98.929054
99.003044
99.890846
99.964836

How do I check if the value after the dot is 0, for example:

99.003044 or 98.041245
Stan
  • 191
  • 7

4 Answers4

7

Because modf returns the fractional part with the same sign as its argument, use the absolute value of the fraction for testing:

#include <cmath>

bool check(double x)
{
    double integral, fractional;
    fractional = std::modf(x, &integral);
    return std::abs(fractional) < 0.1;
}
Michael Roth
  • 186
  • 6
  • Perfect point about sign testing! – SergeyA Aug 08 '19 at 19:38
  • 1
    Good solution in general, but with floating points there are always edge cases. E.g. this solution works unexpectedly for `10.1`: https://wandbox.org/permlink/nfwDkAWRzGPPv4Q3 – Mikhail Aug 08 '19 at 19:45
  • @Mikhail I would believe that `10.1` is not representable in double, and as such, not a valid case? :) – SergeyA Aug 08 '19 at 19:47
  • @SergeyA Neither is `0.1`! That's what I'm saying. The question is a bit ambiguous. That's why I'm not saying it is "incorrect", but rather "unexpected". – Mikhail Aug 08 '19 at 23:14
3

There are a couple ways you can do this. First, you can use std::modf and test if the return value, which is the decimal part, is less than .1. That would look like

bool check(double value)
{
    double discard;
    return std::abs(std::modf(value, &discard)) < .1;
    // need the abs for cases when value is negative.
}

Another option would be to subtract the whole part out of the value and see if that is less than .1. You can do that using std::floor like

bool check(double value)
{
    return (value - std::floor(value)) < .1;
}

Do note that at least for some values both of thses solutions fail because of floating point inaccuracy. To avoid that you can use the bullet proof solution of converting the value to a string, and then testing if there is a 0 after the decimal point. That would look like

bool check(double value)
{
    std::string str = std::to_string(value);
    size_t pos = str.find(".");
    if (pos == std::string::npos)
        return true; // not sure if you want to return true or false for whole numbers
    return str[pos + 1] == '0';
}
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Does not work for e.g. `10.1`: https://wandbox.org/permlink/CIXoYpvj6Ggap4lo – Mikhail Aug 08 '19 at 19:39
  • Your `modf`test doesn't account for lesser than 0 fractionals. – SergeyA Aug 08 '19 at 19:39
  • @SergeyA Good point. Fixed. – NathanOliver Aug 08 '19 at 19:41
  • I would not call it a bullet-proof solution. If `10.1` is not representable as double, than the question of whether it's representation contains leading decimal 0 is moot. Consider this: https://wandbox.org/permlink/0R4BqHnfHL1tq7Wy – SergeyA Aug 08 '19 at 19:48
  • @Mikhail Thanks for finding a value that it didn't work for. I wasn't sure if floating point inaccuracy would crop up in this. I've added a string solution that should not have an issue with it. – NathanOliver Aug 08 '19 at 19:49
  • @SergeyA That's a weird attitude to have. There is an infinite number of values that can't be exactly represted as a `double`. Do we need to throw all of those out? – NathanOliver Aug 08 '19 at 19:52
  • But there is no such thing as `10.1`. It just doesn't exist. You are getting expected result only because `to_string` is not precise enough in your implementation. If it were to use a different precision (and I do not think precision is guaranteed for `to_string`) you'd get a different value. Consider the link I put in my comment above (after edit). – SergeyA Aug 08 '19 at 19:55
1

The only 100% safe way of doing this is to extract this information from the double value itself. Multiplications by 10 (as suggested in the comments) can lead to numerical overflow, as well as getting integer part of the value by casting to integer.

Luckily, C++ has the facilities for that, std::modf. It gives you fractional part of the double number, which you can than compare with 0.1.

(As other answer suggests, you can also use std::floor).

SergeyA
  • 61,605
  • 5
  • 78
  • 137
1

It seems that most of the solutions given won't work for the input 10.1. Here's what I have achieved:

Code:

#include <iostream>
#include <string>
#include <cmath>    // std::abs

void isZero(double x){
    double total = std::abs (x) - std::abs((int)x);
    //std::cout << "Decimal Number: " << total << "\n";
    if(((total * 10 + 0.5) / 10.0) < 0.10){
        std::cout << x << " True\n";
    }else{
        std::cout << x << " False\n";
    }
}

int main(){
    double positive[] = { 
        96.487587, 96.561569, 97.893280, 97.967270, 
        98.041245, 98.115227, 98.855072, 98.929054, 
        99.003044, 99.890846, 99.964836, 10.0, 10.1, 
        10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9,
    };
    double negative[] = { 
        -96.487587, -96.561569, -97.893280, -97.967270, 
        -98.041245, -98.115227, -98.855072, -98.929054, 
        -99.003044, -99.890846, -99.964836, -10.0, -10.1, 
        -10.2, -10.3, -10.4, -10.5, -10.6, -10.7, -10.8, -10.9
    };

    for(int i = 0; i <  sizeof(positive)/sizeof(positive[0]); i++){
        isZero(positive[i]);
    }

    for(int i = 0; i <  sizeof(negative)/sizeof(negative[0]); i++){
        isZero(negative[i]);
    }
}

Output:

96.4876 False
96.5616 False
97.8933 False
97.9673 False
98.0412 True
98.1152 False
98.8551 False
98.9291 False
99.003 True
99.8908 False
99.9648 False
10 True
10.1 False
10.2 False
10.3 False
10.4 False
10.5 False
10.6 False
10.7 False
10.8 False
10.9 False
-96.4876 False
-96.5616 False
-97.8933 False
-97.9673 False
-98.0412 True
-98.1152 False
-98.8551 False
-98.9291 False
-99.003 True
-99.8908 False
-99.9648 False
-10 True
-10.1 False
-10.2 False
-10.3 False
-10.4 False
-10.5 False
-10.6 False
-10.7 False
-10.8 False
-10.9 False
M. Al Jumaily
  • 731
  • 1
  • 6
  • 21