2
int Distance() {
    char from[40], to[40];
    double val;
    double result;

    printf("Enter what you want to convert from: ");
    fflush(stdin);
    fgets(from, 40, stdin);

    printf("Enter what you want to convert to: \n");
    fflush(stdin);
    fgets(to, 40, stdin);

    printf("Enter the numerical value of conversion: \n");
    scanf("%lf", &val);

    (strcmp(from, "cm") == 0 && strcmp(to, "mm") == 0) ? printf("Answer is %lf MilliMeters", val / 10) : 
    (strcmp(from, "mm") == 0 && strcmp(to, "cm") == 0) ? printf("Answer is %lf CentiMeters", val * 10) :
    (strcmp(from, "cm") == 0 && strcmp(to, "km") == 0) ? printf("Answer if %lf KiloMeters", val * 100000) : 
    (strcmp(from, "km") == 0 && strcmp(to, "cm") == 0) ? printf("Answer is %lf CentiMeters", val / 100000) : 
    (strcmp(from, "mm") == 0 && strcmp(to, "km") == 0) ? printf("Anser is %lf KiloMeters", val  * 1000000) : 
    (strcmp(from, "km") == 0 && strcmp(to, "mm") == 0) ? printf("Answer is %lf  MilliMeters", val / 1000000) : 
    printf("Please enter valid conversion units");
}

I am trying to make a unit converter. So I have made different functions for different conversions - like int distance(), int time() and so on.

In my function to calculate distances, I used fgets() to get multi-char inputs like 'cm', 'mm, etc. What i am trying to achieve is If value of fgets in char from is "any string" and fgets in char to is "any other string": print result whose formula is -----(something)

It doesn't compute the result and directly jumps to the final printf("Please enter valid conversion units")

I am still learning at a beginner level, it could be a minor mistake, Please help.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • 2
    `fgets` leaves the newline character at the end of the input, you need to get rid of it somehow. FYI `fflush(stdin);` is undefined behaviour. – n. m. could be an AI Jul 06 '23 at 10:51
  • yeah i tried that. But without fflush(stdin), other problem arises. without fflush, the function never takes any fgets() and directly jumps to printf() – sumiicode.exe Jul 06 '23 at 11:00
  • Most likely you used a scanf elsewhere in your code which left a '\n' in the input. Don't mix them. – stark Jul 06 '23 at 11:10
  • "But without fflush(stdin), other problem arises" Your problem is with mixing `fgets` and `scanf`. If you search StackOverflow questions and answers, you'll find numerous examples. Don't use `scanf`. Always use `fgets` for any kind of input, then parse the line with `sscanf` if needed (and always check the return value of `sscanf`). – n. m. could be an AI Jul 06 '23 at 11:10
  • OT: You could be a bit smarter by converting the unit prefix to a signed integer (c=>-2, k=>3, ...), and calculate 10**(difference in exponent) to get the factor between the 2. Also, check the return value of `scanf()`, `fgets()`, ... – 12431234123412341234123 Jul 06 '23 at 11:53
  • 1
    Besides the [`scanf()` leaves the newline char in the buffer](https://stackoverflow.com/questions/5240789/scanf-leaves-the-new-line-char-in-the-buffer), the `fgets()` takes its trailing newline as part of the string, so your units comparisons will never match. Please see [Removing trailing newline character from `fgets()` input](https://stackoverflow.com/questions/2693776/removing-trailing-newline-character-from-fgets-input/28462221#28462221). The two functions have entirely different behaviour and should not be mixed. – Weather Vane Jul 06 '23 at 15:00

4 Answers4

1
const char* mapper[6][3] = {
    {"cm", "mm", "millimeters"},
    {"cm", "km", "kilometers"},
    {"mm", "cm", "centimeters"},
    {"mm", "km", "kilometers"},
    {"km", "cm", "centimeters"},
    {"km", "mm", "millimeters"},
};

for(int i=0; i<6; i++)
{
    if(strcmp[from, mapper[i][0]] != 0) continue;
    if(strcmp[to, mapper[i][1] != 0) continue;
    printf("answer is %lf, %s", val, mapper[i][2]);
    break;
}

You can declare standard and constant messages. then use that array to sequentially search for it. It is more clean.

Also use scanf cause it doesn't adds the newline character to input or if you really want to use fgets. Then just use buffer size of 3. For e.g.


char from[3];
fgets(from, 3, stdin);


EDIT:

And to perform conversion

const char* mapper[6][4] = {
    {"cm", "mm", "millimeters", "10"},
    {"cm", "km", "kilometers", "0.00001"},
    {"mm", "cm", "centimeters", "0.1"},
    {"mm", "km", "kilometers", "0.000001"},
    {"km", "cm", "centimeters", "100000"},
    {"km", "mm", "millimeters", "1000000"},
};

for(int i=0; i<6; i++)
{
    if(strcmp[from, mapper[i][0]] != 0) continue;
    if(strcmp[to, mapper[i][1] != 0) continue;
    printf("answer is %lf, %s", val * atof(mapper[i][3]), mapper[i][2]);
    break;
}
hsuecu
  • 107
  • 8
1

There are multiple issues in the code:

  • fgets() stores the trailing newline at the end of the target array, so all comparisons with unit strings will fail.
  • you should use an intermediary unit to avoid having to test all combinations.

Here is a modified version:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct unit {
    double factor;
    const char *abbrev, *singular, *plural;
} const units[] = {
    { 1, "mm", "millimeter", "millimeters" },
    { 10, "cm", "centimeter", "centimeters" },
    { 100, "dm", "decimeter", "decimeters" },
    { 1000, "m", "meter", "meters" },
    { 10000, "dam", "decameter", "decameters" },
    { 100000, "hm", "hectometer", "hectometers" },
    { 1000000, "km", "kilometer", "kilometers" },
    { 25.4, "in", "inches", "inches" },
    { 304.8, "ft", "foot", "feet" },
    { 914.4, "yd", "yards", "yards" },
    { 201168, "fur", "furlong", "furlongs" },
    { 1609344, "mi", "mile", "miles" },
};

int get_unit(void) {
    char buf[40];
    while (fgets(buf, sizeof buf, stdin)) {
        buf[strcspn(buf, "\n")] = '\0';   // remove the newline if any
        for (int i = 0, n = sizeof(units) / sizeof(*units); i < n; i++) {
            if (!strcmp(buf, units[i].abbrev)
            ||  !strcmp(buf, units[i].singular)
            ||  !strcmp(buf, units[i].plural)) {
                return i;
            }
        }
        printf("unknown unit\n");
    }
    printf("unexpected end of file, aborting\n");
    exit(1);
}

int main(void) {
    int from, to;
    double val;

    printf("Enter what you want to convert from: ");
    from = get_unit();

    printf("Enter what you want to convert to: ");
    to = get_unit();

    printf("Enter the numerical value of conversion: ");
    if (scanf("%lf", &val) == 1) {
        double result = val * units[from].factor / units[to].factor;
        printf("Answer is %g %s\n", result, result == 1 ? units[to].singular : units[to].plural);
        return 0;
    }
    return 1;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
0

Instead of having a conversion ratio for every pair of units, do it in two steps: first convert the input to meters, then convert from meters to the output unit.

If you have N possible units, this approach will only require N conversion ratios, whereas doing each pair separately requires N^2.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

struct Ratio {
    const char *name;
    const char *abbrev;
    double meters;
};

// This should really be a hash table, but one thing at a time
struct Ratio ratios[] = {
    { "millimeters", "mm", 0.001 },
    { "centermeters", "cm", 0.01 },
    { "meters", "m", 1.0 },
    { "kilometers", "km", 1000.0 },
};

size_t get_ratio(const char *unit) {
    const size_t N = sizeof(ratios) / sizeof(struct Ratio);
    for (size_t idx = 0; idx < N; idx++) {
        if (0 == strcmp(unit, ratios[idx].abbrev)) return idx; 
    }
    fprintf(stderr, "Unrecognized unit: %s\n", unit);
    return SIZE_MAX;
}

void convert(const char *from_unit, const char *to_unit, double val) {
    size_t conv0 = get_ratio(from_unit);
    size_t conv1 = get_ratio(to_unit);
    if (SIZE_MAX == conv0 || SIZE_MAX == conv1) return;
    printf("answer is %f %s\n",
        val * ratios[conv0].meters / ratios[conv1].meters,
        ratios[conv1].name);
}

Note that this also correctly handles the case where your input and output units are the same.

Ray
  • 1,706
  • 22
  • 30
  • To avoid precision issues, you might want to use millimeter as the intermediary unit. – chqrlie Jul 06 '23 at 19:59
  • @chqrlie Shouldn't make any real difference at double precision (you've got 52 significant bits in the mantissa no matter the exponent, and the exponent is only spanning the middle ~20 values of the 2048 in its range), but it can't hurt. – Ray Jul 06 '23 at 22:50
0

You can use a hash table and check if your string is contained in it. You'll be able to get which of the strings is the one selected. On each entry you can save also the multiplication facto to do unit conversion or whatever information is required. Search is fast (it's independent of the actual number of strings in the table) and will be very efficient.

Luis Colorado
  • 10,974
  • 1
  • 16
  • 31