Почему этот код C возвращает неожиданные значения?

Я хочу умножить каждую вторую цифру на 2, начиная со предпоследней цифры числа, а затем сложить вместе цифры этих продуктов, но первые напечатанные значения кажутся совершенно бессмысленными.

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

int len(long li);

int main(void)
{
    // Get credit card number
    long credit_card = 378282246310005;

    // Adding every second digit's double's digits, starting with the second to last
    int sum = 0;
    int digit_doubled;
    for (int i = 0; i < len(credit_card); i++)
    {
        if (i % 2 == 0)
        {
            continue;
        }
        digit_doubled = (int) (credit_card / pow(10, i)) % 10 * 2;
        printf("Digit doubled %i: %i\n", i, digit_doubled);
        for (int j = 0; j < len(digit_doubled); j++)
        {
            sum += (int) (digit_doubled / pow(10, j)) % 10;
        }
    }
}

int len(long li)
{
    if (li == 0)
    {
        return 1;
    }
    return floor(log10(li)) + 1;
}

Я попытался изменить выражение, чтобы посмотреть, какие результаты я получу. Когда я удалил % 10 * 2 с конца digit_doubled = (int) (credit_card / pow(10, i)) % 10 * 2; я получил результаты, которые указывают на какое-то целочисленное переполнение, но я совершенно не понимаю, откуда оно могло взяться, поскольку моя программа на самом деле нигде не выдает никаких высоких значений.

При запросе помощи по отладке, включая минимально воспроизводимый пример. u не объявлен в функции len, поэтому этот код не скомпилируется. Похоже, что long li должно быть long u, как будто этот код является результатом применения OCR к тексту. 378282246310005, вероятно, слишком велик для long в вашей реализации C. Не используйте целочисленные типы для кредитных карт; обрабатывать их как строки символов (символы, которые являются цифрами). Избегайте использования pow (поскольку некоторые реализации pow неадекватны) или других операций с плавающей запятой для целочисленной арифметики; напишите свою собственную целочисленную процедуру или спроектируйте код, не требующий возведения в степень.

Eric Postpischil 13.02.2023 02:00

Банальное упрощение: 2*5 + 2*3 + 2*7 == 2 * (5 + 3 + 7)... Не усложняйте себе жизнь.

Fe2O3 13.02.2023 02:07

@EricPostpischil, извините за имя параметра. Это было редактирование в последнюю минуту, и в моем реальном коде такого не было. Я попытался напечатать переменную «credit_card» после ее инициализации, и кажется, что информация не теряется. Я должен использовать длинное целое из-за моих инструкций к курсам. Почему вы не должны использовать pow для целочисленных степеней и что такое целочисленная процедура?

Alan 13.02.2023 02:10

@ Fe2O3, если результат умножения больше 10, я также должен добавить две цифры результата, так что это не сработает.

Alan 13.02.2023 02:11

Ах! так что вы действительно хотите только if ( x >= 10 ) вместо внутреннего loop. Опять же, зачем усложнять? 2 * 3 = 6 так что никаких "складываний"... 2 * 8 = 16 так что вы хотите 1+6=7 в качестве значения, кажется... Вызов цикла и pow() кажется излишним для такого простого и ограниченного вычисления... ПОЦЕЛУЙ... Удвоение каждого однозначного значения 0-9 приводит к однозначному значению... Попробуйте вместо этого простую "таблицу поиска", возможно...

Fe2O3 13.02.2023 02:17

@Alan: Некоторые реализации pow возвращают числа, такие как 99.9999999999999857891452847979962825775146484375 для pow(10, 2) или аналогичные случаи, а затем преобразование в целое число дает 99, а не 100. «Целая процедура» — это процедура с целочисленными параметрами и целочисленным типом возвращаемого значения, которая выполняет целочисленную арифметику. .

Eric Postpischil 13.02.2023 02:25

@EricPostpischil, я новичок в C. Не могли бы вы объяснить, как эти процедуры будут работать?

Alan 13.02.2023 02:49

@Alan Обязательно примите лучший ответ, нажав на галочку, чтобы мы знали, что у вас все готово.

Allan Wind 13.02.2023 05:17

Обрабатывать цифры в CCN (номере кредитной карты) намного проще, если рассматривать его как строку, а не как число. У вас уже есть цифры, разделенные для вас.

Jonathan Leffler 13.02.2023 05:18
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
9
203
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

В первом цикле, когда i=1 вы приводите значение, превышающее максимальное значение, которое может быть сохранено в целом числе (2147483647), это приводит к его усечению и в моей системе становится отрицательным:

digit_doubled = (int) (credit_card / pow(10, i)) % 10 * 2; // =>
digit_doubled = (int) 37828224631000 % 10 * 2; / =>
digit_doubled = -1847312168 % 10 * 2; // =>
digit_doubled = -16;

Я предлагаю вам включить stdint.h и использовать uint64_t вместо того, чтобы молча предполагать, что тип long равен 64 битам. В качестве альтернативы рассмотрите возможность использования строки в качестве номера кредита, который является идентификатором, а не числом (даже если он записывается как единица).

Функции смешивания, которые работают с значениями типа double для целочисленного типа, откроют вам доступ к ошибкам округления с плавающей запятой. Вы можете использовать uint64_t версии этих функций вместо double. Ниже я реализовал для вас функции len() и my_pow10(). Поскольку значение digit_doubled не превышает 18, вы можете просто встроить это вычисление вместо использования цикла.

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

uint64_t my_pow10(uint64_t x) {
    if (x == 0) return 1;
    size_t v = 10;
    for(size_t i = 1; i < x; i++, v *= 10);
    return v;
}

size_t len(uint64_t v) {
    size_t i = 0;
    for(; v; i++, v /= 10);
    return i;
}

int main(void) {
    const uint64_t credit_card = 378282246310005;
    const uint8_t credit_card_len = len(credit_card);
    uint8_t sum = 0;
    for (uint8_t i = 1; i < credit_card_len; i += 2) {
        uint8_t digit_doubled = credit_card / my_pow10(i) % 10 * 2;
        printf("Digit doubled %hhu: %hhu\n", i, digit_doubled);
        sum += digit_doubled / 10 + digit_doubled % 10;
    }
    printf("sum = %u\n", sum);
}

Тьфу, почему кредит_карта не строка? Это оскорбляет мои чувства.

Joshua 13.02.2023 02:45

@Joshua Джошуа, потому что вы выбрали это, однако, я думаю, вам следует привести более сильный аргумент. Как будто это идентификатор, а не номер.

Allan Wind 13.02.2023 02:46

Я очень новичок в C. Я не понимаю, что вы имеете в виду. Не могли бы вы просто сказать мне, что заставляет мой код возвращать совершенно бессмысленные значения?

Alan 13.02.2023 02:51

@ Алан, я расширил объяснение. Это помогло?

Allan Wind 13.02.2023 02:59

@AllanWind Приведение к (int) перед выполнением % 10 — одна из основных проблем; другой использует pow

M.M 13.02.2023 03:09

@М.М. Я согласен. Что бы вы хотели, чтобы я разъяснил?

Allan Wind 13.02.2023 03:13

Извините, это было предназначено для пометки OP

M.M 13.02.2023 03:15

Предложив использовать LUT для доставки однозначного значения, требуемого вопросом OP, вот фрагмент кода, чтобы сделать именно это:

unsigned long long copy = longValue; // perhaps correct on OP's compiler??

copy /= 10; // dispose of the checksum digit (rightmost)

// The following line depends on the OP's problem statement
// Is this digit odd (checksum digit being digit zero)
// or is this digit even? (checksum digit being disregarded.)
// Incl-/excl- next line to suit problem statement.
copy /= 10; // dispose of the rightmost "odd" digit

while( copy ) {
    int digit = copy % 10; // get "even" digit from the right end.

    // Crafted LUT to return correctly "doubled & folded" value of this digit.
    sum += "0246813579"[digit] - '0';

    copy /= 100; // "shift" value down by 100 (ie: 2 digits of value)
}

Я оставляю это в качестве упражнения для ОП, чтобы определить, что характеризует «четное» и «нечетное» в последовательности цифр «номера» кредитной карты. Считается ли цифра контрольной суммы «1» или нет? Чтобы ОП работал...

Ради интереса, вот цикл после сжатия.

for( /**/; copy; copy /= 100 )
    sum += "0246813579"[ copy % 10 ] - '0';

И более продвинутая версия просто вычислит желаемое значение из предоставленного значения:

for( /**/; copy; copy /= 100 )
    sum += copy%5 * 2 + copy%10/5;

Для тех, кто (разумно) сомневается в правильности приведенной выше формулы:

#include <stdio.h>

int main( void ) {
    for( int i = 0; i < 10; i++ ) {
        int j = 2 * i;

        printf( "Digit Value: %d ", i );
        printf( " x2 = %02d ", j );
        printf( "==> %d + %d ", j/10, j%10 );
        printf( "==> %d ", j/10 + j%10 );

        printf( "==== %d\n", i%5 * 2 + i%10/5 ); // <== Formula version
    }
    return 0;
}
Digit Value: 0  x2 = 00 ==> 0 + 0 ==> 0 ==== 0
Digit Value: 1  x2 = 02 ==> 0 + 2 ==> 2 ==== 2
Digit Value: 2  x2 = 04 ==> 0 + 4 ==> 4 ==== 4
Digit Value: 3  x2 = 06 ==> 0 + 6 ==> 6 ==== 6
Digit Value: 4  x2 = 08 ==> 0 + 8 ==> 8 ==== 8
Digit Value: 5  x2 = 10 ==> 1 + 0 ==> 1 ==== 1
Digit Value: 6  x2 = 12 ==> 1 + 2 ==> 3 ==== 3
Digit Value: 7  x2 = 14 ==> 1 + 4 ==> 5 ==== 5
Digit Value: 8  x2 = 16 ==> 1 + 6 ==> 7 ==== 7
Digit Value: 9  x2 = 18 ==> 1 + 8 ==> 9 ==== 9

Здесь вам вообще не нужна арифметика с плавающей запятой, журналы или pows. Вы можете использовать % 10, чтобы извлечь следующую цифру, и / 10, чтобы отбросить ее. Так:

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

int main(void)
{
    uint64_t credit_card = 378282246310005ULL;

    int sum = 0;
    while (credit_card != 0)
    {
        credit_card /= 10; // Discard rightmost digit
        int double_digit = 2 * (credit_card % 10);
        credit_card /= 10;
        sum += double_digit % 10;
        double_digit /= 10;
        sum += double_digit % 10;
    }
    printf ("%d\n", sum);
}

@ Fe2O3: спасибо, что указали на это! Я исправил это сейчас.

TonyK 13.02.2023 03:25

Другие вопросы по теме