c++ på linux - får icke-deskriptivt "floating point exception" i program

Permalänk
Medlem

c++ på linux - får icke-deskriptivt "floating point exception" i program

Hej,

Jag håller på med Problem 33 från Project Euler och får en bugg i någon av mina funktioner som ger ett fel som nedan:

alexl@PD70PNP:/mnt/Storage_SSD/C++_Projects/denom_val$ ./denom_val Floating point exception alexl@PD70PNP:/mnt/Storage_SSD/C++_Projects/denom_val$

main.cpp:

#include "include/functions.h" #include <iostream> /* The fraction 49/98 is a curious fraction, as an inexperienced mathematician in attempting to simplify it may incorrectly believe that 49/98 = 4/8, which is correct, is obtained by cancelling the 9s. We shall consider fractions like, 30/50 = 3/5, to be trivial examples. There are exactly four non-trivial examples of this type of fraction, less than one in value, and containing two digits in the numerator and denominator. If the product of these four fractions is given in its lowest common terms, find the value of the denominator. */ int main() { std::cout << "The answer: " << func::get_answer() << '\n'; return 0; }

functions.cpp:

#include "include/functions.h" #include <string> #include <algorithm> #include <vector> #include <numeric> namespace func { bool is_curious_fraq(func::frac f) { std::string numerator = std::to_string(f.numerator); std::string denominator = std::to_string(f.denominator); bool shares_digit = false; char common_char = ' '; bool result = false; //Does numerator and denominator share a digit?. for (int i = 0; i < static_cast<int>(numerator.length()); i++) { char test_char = numerator[i]; if (denominator.find(test_char) != std::string::npos) { shares_digit = true; common_char = test_char; } } if (shares_digit) { numerator.erase(std::find(numerator.begin(), numerator.end(), common_char)); denominator.erase(std::find(denominator.begin(), denominator.end(), common_char)); double cancelled_frac = std::stod(numerator) / std::stod(denominator); double original_frac = static_cast<double>(f.numerator) / static_cast<double>(f.denominator); result = (cancelled_frac == original_frac); } else { result = false; } return result; } void reduce_frac(func::frac &f) { int gcd_res = std::gcd(f.numerator, f.denominator); int temp_numerator = f.numerator / gcd_res; int temp_denominator = f.denominator / gcd_res; f = {.numerator = temp_numerator, .denominator = temp_denominator}; } int get_answer() { std::vector<func::frac> fracs; //Find the candidate fractions. for (int a = 11; a <= 98; a++) { if (a % 10 != 0) { for (int b = 12; b <= 99; b++) { if (b % 10 != 0) { func::frac f = {.numerator = a, .denominator = b}; if (is_curious_fraq(f)) { fracs.push_back(f); } } } } } //Multiply candidate fractions together. int numerator_product = 1; int denominator_product = 1; for (func::frac f : fracs) { numerator_product = numerator_product * f.numerator; denominator_product = denominator_product * f.denominator; } //Declare and initialize product fraction struct. func::frac product_fraq = {.numerator = numerator_product, .denominator = denominator_product}; //Reducing product fraction and returning denominator. reduce_frac(product_fraq); return product_fraq.denominator; } }

functions.h:

#ifndef FUNCTIONS_H #define FUNCTIONS_H namespace func { struct frac { int numerator; int denominator; }; bool is_curious_fraq(func::frac f); void reduce_frac(func::frac &f); int get_answer(); } #endif

och specifikationen på min maskin:

alexl@PD70PNP:~$ neofetch _,met$$$$$gg. alexl@PD70PNP ,g$$$$$$$$$$$$$$$P. ------------- ,g$$P" """Y$$.". OS: Debian GNU/Linux 12 (bookworm) x86_64 ,$$P' `$$$. Host: PD5x_7xPNP_PNR_PNN_PNT ',$$P ,ggs. `$$b: Kernel: 6.1.0-16-amd64 `d$$' ,$P"' . $$$ Uptime: 12 days, 20 hours, 32 mins $$P d$' , $$P Packages: 1893 (dpkg) $$: $$. - ,d$$' Shell: bash 5.2.15 $$; Y$b._ _,d$P' Resolution: 1920x1080 Y$$. `.`"Y$$$$P"' DE: GNOME 43.9 `$$b "-.__ WM: Mutter `Y$$ WM Theme: Adwaita `Y$$. Theme: Adwaita [GTK2/3] `$$b. Icons: Adwaita [GTK2/3] `Y$$b. Terminal: gnome-terminal `"Y$b._ CPU: 12th Gen Intel i7-12700H (20) @ 4.600GHz `""" GPU: Intel Alder Lake-P GPU: NVIDIA GeForce RTX 3060 Mobile / Max-Q Memory: 3430MiB / 15694MiB

Jag kan inte komma på var felet verkar uppstå, alla flyttalsoperationer går bra för sig men inte i sin helhet. Kan någon hjälpa mig?

Permalänk
Hedersmedlem

Du bör kunna få fram exakt var felet sker om du kompilerar om med debuginfo och kör gdb.

g++ -o denom_val *.cpp -ggdb3
gdb ./denom_val
(inuti gdb) run

Så bör den visa exakt vilken rad som triggade problemet.

(gdb) run Starting program: /home/serenity/test/test [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Program received signal SIGFPE, Arithmetic exception. 0x0000555555555178 in main () at test.cpp:4 4 std::cout << 1/0 << std::endl; (gdb)

Visa signatur

Asus ROG STRIX B550-F / Ryzen 5800X3D / 48 GB 3200 MHz CL14 / Asus TUF 3080 OC / WD SN850 1 TB, Kingston NV1 2 TB + NAS / Corsair RM650x V3 / Acer XB271HU (1440p165) / LG C1 55"
Mobil: Moto G200

Permalänk
Medlem

Missvisande felmeddelande troligen, du försöker göra division med noll någonstans. Se exempelvis https://www.reddit.com/r/C_Programming/comments/q1cztm/intege...
Jag har bara ögnat igenom koden lite snabbt men jag gissar att om du matar in 31/30 så kommer du radera 3orna och därmed försöka dividera 1 med 0, men jag kanske kan ha missat nåt.

Kan starkt rekommendera att använda debugger för att enkelt hitta varför exempelvis exception kastas eller varför din kod inte gör vad du tycker att den borde, antingen en debugger inbyggd i din IDE (mer användarvänligt men kan behöva lite konfiguration för att få till det) eller använda gdb enligt tipset ovan.

Permalänk
Medlem

Problemet är att när du beräknar numerator_product och denominator_product i get_answer så multiplicerar du alla täljare/nämnare med varandra. Summorna ökar snabbt när du gör på det sättet och överflödar dina int-variabler efter bara några multiplikationer, vilket senare orsakar division med 0 i reduce_frac.

Permalänk
Medlem
Skrivet av Thomas:

Du bör kunna få fram exakt var felet sker om du kompilerar om med debuginfo och kör gdb.

g++ -o denom_val *.cpp -ggdb3
gdb ./denom_val
(inuti gdb) run

Så bör den visa exakt vilken rad som triggade problemet.

(gdb) run Starting program: /home/serenity/test/test [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Program received signal SIGFPE, Arithmetic exception. 0x0000555555555178 in main () at test.cpp:4 4 std::cout << 1/0 << std::endl; (gdb)

Hur anpassar jag kommandot för projektet? Repot:
https://github.com/AlexLandherr/denom_val

Permalänk
Hedersmedlem
Skrivet av Apollo11:

Hur anpassar jag kommandot för projektet? Repot:
https://github.com/AlexLandherr/denom_val

Ändra i Makefile så att CXXFLAGS blir
CXXFLAGS = -std=c++20 -Wall -ggdb3

Men om du använder ett IDE/en kodeditor (VS Code eller liknande) så är det ju som sagt lättare att använda inbyggda funktioner där.

Fast du verkar ju ha ett svar på just denna frågan ovan.

Visa signatur

Asus ROG STRIX B550-F / Ryzen 5800X3D / 48 GB 3200 MHz CL14 / Asus TUF 3080 OC / WD SN850 1 TB, Kingston NV1 2 TB + NAS / Corsair RM650x V3 / Acer XB271HU (1440p165) / LG C1 55"
Mobil: Moto G200

Permalänk
Medlem
Skrivet av Thomas:

Ändra i Makefile så att CXXFLAGS blir
CXXFLAGS = -std=c++20 -Wall -ggdb3

Men om du använder ett IDE/en kodeditor (VS Code eller liknande) så är det ju som sagt lättare att använda inbyggda funktioner där.

Fast du verkar ju ha ett svar på just denna frågan ovan.

Fick detta och har bytt int till int64_t (ändringarna ligger uppe nu på repot):

Program received signal SIGFPE, Arithmetic exception. 0x000055555555656f in func::reduce_frac (f=...) at src/functions.cpp:54 54 int64_t temp_numerator = f.numerator / gcd_res;

Permalänk
Medlem
Skrivet av Apollo11:

Fick detta och har bytt int till int64_t (ändringarna ligger uppe nu på repot):

En int64_t kan max lagra upp till ca 9.2*1018, men t.ex. denominator_product blir ca 9.7*10145 enligt din beräkning. C++ har inga heltalstyper ens i närheten stora nog för det, så lösningen är troligtvis att ändra på din algoritm så att du inte behöver lagra såna enorma tal.

Permalänk
Medlem
Skrivet av perost:

En int64_t kan max lagra upp till ca 9.2*1018, men t.ex. denominator_product blir ca 9.7*10145 enligt din beräkning. C++ har inga heltalstyper ens i närheten stora nog för det, så lösningen är troligtvis att ändra på din algoritm så att du inte behöver lagra såna enorma tal.

Bra poäng, ska kolla in det.

Permalänk
Datavetare
Skrivet av perost:

Problemet är att när du beräknar numerator_product och denominator_product i get_answer så multiplicerar du alla täljare/nämnare med varandra. Summorna ökar snabbt när du gör på det sättet och överflödar dina int-variabler efter bara några multiplikationer, vilket senare orsakar division med 0 i reduce_frac.

En kommentar till @Apollo11 kring ovan: vad du lyckats träffa här är en klass av buggar som kallas undefined behavior. Som tur i detta fall råkar beteendet på Linux/x86_64 vara ett HW-exception, i andra fall hade potentiell något helt annat hänt (på Linux/ARM64 får man svaret 0 utan krasch, i alla fall med g++...).

Sen att du hamnar där: lusläs uppgiften... Hur många tal kan du maximalt behöva multiplicera?

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Medlem

Löste problemet; visade sig att looparna som tog fram bråken släppte igenom 87 istället för 4 st bråk.

Lösningen finns nu på GitHub.