BenB
BenB

Reputation: 1050

Get printf to ignore the negative sign on values of zero

I'm trying to write a (mostly)* C program that sorts numerical results and eliminates duplicates. The results are stored as STRUCTS that contain a string, an integer, and 4 doubles. The doubles are what is relevant for determining if two results are duplicates.

To do this, I sprintf a string using the 4 doubles to some precision i.e.

    #define PRECISION 5
sprintf(hashString, "%.*lf %.*lf %.*lf %.*lf", PRECISION, result.v1, PRECISION, result.v2, PRECISION, result.v3, PRECISION, result.v4);

I then use this as a hashkey for a tr1::unordered_map<string, ResultType>. Then the program checks to see if the hashtable already contains an entry for that key, if so, the result is a duplicate and can be discarded. Otherwise, it gets added to the hashtable.

The problem is that sometimes one of my values will be rounded to zero from, for example, -10E-9, by sprintf; As a result, the string will contain "-0.00000" rather than "0.00000". These two values will obviously generate different hashkeys, despite representing the same result.

Is there something built into sprintf or even the C language that will allow me to deal with this? I've come up with a bit of a work around (see post below) -- but if there's something built in, I would much rather use that.

*the program is written in C because that's the language I'm most comfortable in, but I'll end up compiling with g++ in order to use the unordered_map.

I've come up with the following workaround. But A) I'm hoping there's a builtin solution and B) I don't have a very deep understanding of atof or floating point math, so I'm not sure if the condition if(doubleRepresentation == 0.0) will always trip when it should.

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #define PRECISION 5
    #define ACCURACY 10E-6
    double getRidOfNegZeros (double number)
    {

            char someNumAsStr[PRECISION + 3]; // +3 accounts for a possible minus sign, the leading 0 or 1, and the decimal place.
            sprintf(someNumAsStr, "%.*lf", PRECISION, number);

            double doubleRepresentation = atof(someNumAsStr);
            if((doubleRepresentation < ACCURACY) && (doubleRepresentation > -ACCURACY))
            {
                    doubleRepresentation = 0.0;
            }

            return doubleRepresentation;
    }

    int main()
    {
            printf("Enter a number: \n");
            double somenum;
            scanf("%lf",&somenum);

            printf("The new representation of double \"%.*lf\" is \"%.*lf\"\n", PRECISION, somenum, PRECISION, getRidOfNegZeros(somenum));
            return 0;
    }

Upvotes: 2

Views: 5698

Answers (5)

Greg
Greg

Reputation: 1251

Perhaps implement a utility function to round/snap values to positive zero. Use precision digit count similar to printf style format syntax.

// Prevent display of -0 values by snapping to positive zero
// \a_number original number
// \a_precisionCount number of digits of decimal precision eg. 2 for #.##, 0 for whole integer. Default 0 (whole integer number.)
// \returns number rounded to positive zero if result would have produced -0.00 for precision.
template <class Real>
Real PosZero(const Real& a_number, const int a_precisionCount = 0)
{
    Real precisionValue = Real(0.5) * pow(Real(0.10), Real(a_precisionCount));
    if( (a_number > -abs(precisionValue)) && (a_number < abs(precisionValue)) )
    {
        return +0.0;
    }
    return a_number;
}

Test:

f32 value = -0.049f;
int precision = 4; // Test precision from param
printf("%.0f, %.2f, %.*f", PosZero(value), PosZero(value,2), precision, PosZero(value,precision));

Test output:

"0, -0.05, -0.0490"

This is intended to be a general solution for people wanting to avoid negative zeros in formatted strings. Not specific to the original poster's use of creating a key or hash.

Upvotes: 1

David Hammen
David Hammen

Reputation: 33116

#include <string>

#define PRECISION 5
#define LIMIT 5e-6

std::string string_rep (double x) {
   char buf[32];
   double xtrunc = ((x > -LIMIT) && (x < LIMIT)) ? 0.0 : x;
   std::sprintf (buf, "%.*f", PRECISION, xtrunc);
   return std::string(buf);
}

std::string make_key (double x, double y, double z, double w) {
   std::string strx = string_rep (x);
   std::string stry = string_rep (y);
   std::string strz = string_rep (z);
   std::string strw = string_rep (w);
   return strx + " " + stry + " " + strz + " " + strw;
}

Upvotes: 0

Tom
Tom

Reputation: 10819

If you know that you only care about differences of 0.00001 (based on your definition of PRECISION), you can round the values to integers first. Something like this may work:

#include <math.h>
#include <stdio.h>

#define SCALE 1e5 // instead of PRECISION 5
sprintf(hashString, "%d %d %d %d",
    (int)round(result.v1 * SCALE),
    (int)round(result.v2 * SCALE),
    (int)round(result.v3 * SCALE),
    (int)round(result.v4 * SCALE));

This also requires a bound on the magnitude of the floating-point values. You don't want to overflow your integer values.

You can also bypass the string formatting and simply do the rounding calculations as part of a structure-level hash, as others have suggested.

Upvotes: 1

Adam Rosenfield
Adam Rosenfield

Reputation: 400204

If you're only using this for the purposes of hashing the double values, then don't bother converting them to a string—just hash the double values directly. Any hash library worth its salt will have the ability to hash arbitrary binary blobs of data.

If for some strange reason your hash library only supports null-terminated C strings, then print out the raw bytes of the double value:

// Alias the double value as a byte array
unsigned char *d = (unsigned char *)&result.v1;
// Prefer snprintf to sprintf!
spnrintf(hashString, hashStringLength, "%02x%02x%02x%02x%02x%02x%02x%02x",
         d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7]);
// ...and so on for each double value

This ensures that unequal values will definitely be given unequal strings.

Upvotes: 0

John Zwinck
John Zwinck

Reputation: 249133

Rather than sprintf()ing the doubles to a big string and using that as the key in a map, why not just put your structs into the map? You can do this easily enough if you just write a less-than operator for your structs which considers the floating-point values you want to use as the key. Something like this:

bool operator <(const MyStruct &lhs, const MyStruct &rhs)
{
    return lhs.v1 < rhs.v1 ||
        (lhs.v1 == rhs.v1 && lhs.v2 < rhs.v2); // ...
}

Then you can replace your tr1::unordered_map<string, ResultType> with std::map<ResultType>, and avoid the whole string printing business all together. If you want you can add some epsilon to the comparison function so that numbers that are nearly the same are stably sorted.

Upvotes: 2

Related Questions