>

Mandelbrot 세트의 PPM (Portable Pixmap) 이미지를 생성하기 위해 C 프로그램을 작성했습니다. 이 프로그램은 CI에서 많은 것을 구현합니다 (구조, 오류 처리, 새 라이브러리 사용 (complex.h) 및 파일 (이미지 파일에 완전히 익숙하지 않음)).이 프로그램 작성의 목적은 주로 내 지식을 테스트하십시오.

가능하다면 누군가 내가 여기서 잘한 일과 내가 잘못한 일을 말해 줄 수 있을까요? 그리고 미래에 더 나은 일을하는 방법에 대한 간략한 요약은? 또한 실행 시간을 줄일 수있는 방법을 알고 싶습니다. 모든 의견을 보내 주셔서 감사합니다.

각 기능과 목적에 대해 간략히 요약하고 명확하지 않은 부분을 명확하게하기 위해 의견을 추가했습니다.

//mandelbrot.c - generates a .PPM (Portable Pixmap format) file of the Mandelbrot set with shading
//Still to add: Implement a better file format, optimise to reduce time, 
#include "stdio.h"
#include "complex.h"
#include "math.h"
#define MAX_TESTS 650
int error(const char *message);
struct colour mandelbrot_test(double complex c);
struct colour rgb_gen(int iterations, double complex num);
struct dimensions dim_gen(int height);
struct dimensions
{
    double hinc;
    double winc;
    unsigned int height;
    unsigned int width;
};
struct colour
{
    unsigned int red;
    unsigned int green;
    unsigned int blue;
};
int main(int argc, char *argv[])
{
    if (argc < 3)
    {
        return error("Too few args!\nCorrect usage: program_name image_name image_height");
    }
    char *file_name = argv[1];
    FILE *file;
    double i, j;
    double complex num;
    struct colour rgb;
    struct dimensions dim;
    dim.height = atoi(argv[2]);
    file = fopen(file_name, "w");
    if (!file)
    {
        return error("Unable to access file!\n");
    }
    else if (dim.height < 1000) //values under ~1000px cause scaling issues with the file
    {
        return error("Image cannot be less than 1000 px in height");
    }
    dim = dim_gen(dim.height);
    fprintf(file, "P3\n%d %d\n255\n", dim.width + 1, dim.height); //Magic number, image dimensions and max RGB value
    for (j = -1.0; j <= 1.0; j += dim.hinc)
    {
        for (i = -2.0; i <= 0.5; i += dim.winc)
        {
            num = i + j * I; //Generate the complex number
            rgb = mandelbrot_test(num); //Generate an RGB value for that number
            fprintf(file, "%d %d %d ", rgb.red, rgb.green, rgb.blue); //Print it to the file
        }
        fprintf(file, "\n");
    }
    fclose(file);
    return 0;
}
struct colour mandelbrot_test(double complex c) //Function to test for divergence of a given number
{
    double complex x = 0;
    int i;
    double r_squared = cimag(c) * cimag(c) + creal(c) * creal(c);
    if (r_squared * (8.0 * r_squared - 3.0) < 3.0/32.0 - creal(c)) //Quick test to see if we can bail out early
    {
        return rgb_gen(MAX_TESTS, x);
    } 
    for (i = 1; i < MAX_TESTS; i++) //Actual testing loop
    {
        x *= x;
        x += c;
        if (cimag(x) * cimag(x) + creal(x) * creal(x) >= 4)
        {
            return rgb_gen(i, x);
        }
    }
    return rgb_gen(MAX_TESTS, x);
}
struct colour rgb_gen(int iterations, double complex num) //Function to generate the RGB values for a given complex number
{
    struct colour rgb;  
    if (iterations == MAX_TESTS)
    {
        rgb.red = 0;
        rgb.green = 0;
        rgb.blue = 0;
    }
    else
    {
        double z = sqrt(creal(num) * creal(num) + cimag(num) * cimag(num));
        int brightness = 256.0 * log2(2.75 + iterations - log2(log2(z))) / log2((double)MAX_TESTS); //this generates shading based on how fast the number diverges
        rgb.red = brightness;
        rgb.green = brightness;
        rgb.blue = 255;
    }
    return rgb;
}
struct dimensions dim_gen(int height) //Function to generate 5:2 scale dimensions and width/height increments based on the user given height
{
    struct dimensions dim;
    dim.height = height;
    dim.width = 5.0 * (float)height / 2.0;
    dim.hinc = 1.0 / (0.5 * (float)dim.height);
    dim.winc = dim.hinc / 2.0;
    return dim;
}
int error(const char *message) //Rudimentary error handling
{
    printf("%s", message);
    return 1;
}

  • 답변 # 1

    여러 가지 :

    <올>

    gcc 버전 4.8.3을 사용하여 Linux 호스트 (Centos 7)에서 코드를 컴파일했습니다. 명령 행을 통해 : gcc -std=c99 -pedantic -Wall mandlebrot.c -o mandlebrot -lm  다음과 같은 경고가 나타납니다.

    gcc -std=c99 -pedantic -Wall mandlebrot.c -o mandlebrot -lm
    mandlebrot.c: In function ‘main’:
    mandlebrot.c:42:5: warning: implicit declaration of function ‘atoi’ [-Wimplicit-function-declaration]
    dim.height = atoi(argv[2]);
    
    

    #include <stdlib.h> 를 추가해야합니다  당신의 프로그램에.

    다른 것들이 지적했듯이 시스템 포함 파일에는 따옴표 대신 꺾쇠 괄호가 있어야합니다.

    개인 취향은 사용하기 전에 모든 변수를 초기화하는 것인데,`FILE * file = NULL;

    위치 명령 줄 인수 (예 : 파일 이름, 높이)를 싫어합니다. 당신은 getopt 를보고 싶을 수도 있습니다  Linux \ Unix를 사용하는 경우 라이브러리 (Windows를 사용하는 경우에도 여전히 살펴볼 가치가 있습니다).

    높이가 1000보다 작은 경우 "리소스를 유출"할 수 있습니다. 높이가 1000보다 작은 경우 43에서 연 파일을 절대 닫을 수 없습니다.이 코드 샘플에서 큰 문제는 아니지만, 릴리스하는 습관이 있어야합니다. 당신이 얻는 자원. 이 프로그램의 경우 테스트를 다시 정렬하여 수행 할 수 있습니다.

    if(dim.height < 1000)
    {
          return error("image cannot be less than 1000px in height");
    }
    if(NULL != (file = fopen(file_name, "w")))
    {
         // ... generate mandlebrote image here....
         fclose(file);
    }
    else
    {
        // ... handle failure to open file
    }
    
    

    다른 사람들이 언급했듯이, 구조체를 typedef하면 코드를 더 읽기 쉽게 만들 수 있고 입력 비용을 줄일 수 있습니다.

    c99 호환 컴파일러를 사용한다고 가정하고 // 를 어떻게 사용하고 있는지 확인  한 줄로 된 주석 :) 다음과 같이 for-loops를 작성할 수 있습니다.

    for (double j = -1.0; j <= 1.0; j += dim.hinc)
    {
        for (double i = -2.0; i <= 0.5; i += dim.winc)
        {
            num = i + j * I; 
            rgb = mandelbrot_test(num); 
            fprintf(file, "%d %d %d ", rgb.red, rgb.green, rgb.blue); 
         }
         fprintf(file, "\n");
    }
    
    

    이것은 인덱스 변수의 범위를 루프로 제한합니다.

  • 답변 # 2

    와이즈 비즈  매직 번호는일반PPM을 나타내며 심각한 제한이 있습니다 :

    와이즈 비즈

    당신의 코드는 분명히 그것을 위반합니다.원시PPM (매직 번호 P3 )으로 만드는 것이 좋습니다. 및 색상의 이진 표현).

    No line should be longer than 70 characters.

      P6 를 구현 . complex 를 철자 대신 사용하십시오 .

    cabs()  반복 횟수를 계산하고결과 색상을 계산합니다. 이러한 활동은 관련이 없으며 별도의 기능으로 수행해야합니다.

    이 코드는 내 취향에 맞게 수정되었습니다. 내가 존재할 권리가 있다고 생각하는 유일한 의견은 빠른 수렴 테스트에 관한 것이므로 확대해야한다고 생각합니다. 이 테스트가 작동하는 이유를 이해하는 데 시간이 조금 걸렸습니다.

  • 답변 # 3

    구현중인 알고리즘에 익숙하지 않으므로 C 코드에 대해서만 의견을 작성할 수 있습니다.

    <시간> 와이즈 비즈 와이즈 비즈

    이들은 표준 헤더 파일이므로 올바른 참조 방법은 cimag(c) * cimag(c) + creal(c) * creal(c) 사이입니다. 따옴표가 아닙니다. 예 :

    mandelbrot_test
    
    

     사이의 파일을 참조 할 때
     표준 include 경로에서 해당 파일을 먼저 찾도록 컴파일러에 지시합니다. 
    #include "stdio.h"
    #include "complex.h"
    #include "math.h"
     사용
     컴파일러가 프로젝트 디렉토리에서 파일을 찾도록 지시하므로 로컬 프로젝트 파일에 따옴표를 사용합니다.

    <시간>

    <> 전에 헬퍼 함수를 ​​선언하고 선언 할 수 있다면 함수 프로토 타입을 피하는 것이 좋습니다. . 함수 프로토 타입의 문제점은 코드 복제를 생성하여 유지 보수 오버 헤드가 발생한다는 것입니다.

    <시간>당신은 #include <stdio.h> 수   <> 를 공급하지 않아도되는 당신의 지시   "" 를 선언 할 때마다 태그  또는 main() . 매번 자신을 반복한다는 점이 아니라 YMMV를 선호하는 경향이 있습니다.

    typedef
    
    

    <시간>

    와이즈 비즈  절이 끝에 반환되면 struct 와의 연결을 피해야합니다 . 돌아 오는 것이 더 깨끗합니다. 예 :

    와이즈 비즈 와이즈 비즈

    당신은 그 colour 가 필요하지 않습니다  둘 다 돌아 오니까 그것은 두 개의 독립적 인 dimension 로 나눌 수 있습니다 에스. 이 경우이 방법은 사소한 nitpicking이지만 모든 항목이 반환 될 때 체인이 길면 단순화하는 것이 좋습니다.

    <시간>

    개인적으로 typedef struct { double hinc; double winc; unsigned int height; unsigned int width; } dimensions; typedef struct { unsigned int red; unsigned int green; unsigned int blue; } colour; // 'colour' and 'dimensions' are now first class names and // can be used directly to declare instances. 에 의해 다음과 같은 할당 블록을 정렬하는 것이 더 읽기 쉽습니다.  로그인 :

    if
    
    

    BTW, 위의 캐스트는 else 여야합니다

    를 사용하지 않으므로 s
    다른 곳. 그러나 표현식의 한쪽이 이미 이중이므로 캐스트는 불필요합니다.

    <시간>

    와이즈 비즈  함수는 아마도 if (!file) { return error("Unable to access file!\n"); } else if (dim.height < 1000) //values under ~1000px cause scaling issues with the file { return error("Image cannot be less than 1000 px in height"); } 에 인쇄해야합니다 표준 오류 출력입니다. 와이즈 비즈  (와이즈 와이즈) )는 정상적인 프로그램 출력을 인쇄한다고 가정합니다.

    <시간>

    메모리와 스토리지가 걱정된다면 else 를 저장할 수 있습니다 if 의 s (바이트)  RGB 값이 0과 255 사이이므로 struct입니다.

  • 답변 # 4

    밝기 계산

    다음 코드에 관하여 :

    =
    
    

    struct dimensions dim; dim.height = height; dim.width = 5.0 * (float)height / 2.0; dim.hinc = 1.0 / (0.5 * (float)dim.height); dim.winc = dim.hinc / 2.0; // ^ // aligned here 를 계산할 필요가 없습니다 이 함수를 호출 할 때마다 이미 계산 한 두 줄 안에 있기 때문에 (이 값을 double 라고 부를 것입니다. ). 이미 계산 된 값을 전달하십시오.

    float 를 사용할 필요도 없습니다  여기 error() 이후  == stderr . 또한 stdout 이후  == printf  그리고 unsigned char == colour 더 단순화 할 수 있습니다.

    double z = sqrt(creal(num) * creal(num) + cimag(num) * cimag(num));
    int brightness = 256.0 * log2(2.75 + iterations - log2(log2(z))) / log2((double)MAX_TESTS);
    
    

    그러면 creal(num) * creal(num) + cimag(num) * cimag(num) 를 계산할 필요가 없습니다  또는 제곱근이지만 여전히 r 를 좋아하지 않습니다.  부품. 왜 그것이 있는지 확실하지 않습니다 (해당 줄에 의견이 있지만 공식의 출처는 설명하지 않습니다). 로그를 기록하면 숫자가 정말 작아집니다.- sqrt  3.75입니다. 우리는 r>= 4를 알고 있으며, 작은 상수를 제곱하고 더하기 때문에 16보다 훨씬 높지는 않습니다.

    내가 오해하거나 무언가를 놓치지 않았다면 (이 경우 설명이 필요하다),이 줄은 대략 동일하다 :

    log(sqrt(x))
    
    

    그러나 다음은 256의 배수를 0.0에서 1.0으로 정확하게 변경한다는 것을 알 수 있기 때문에 다음이 약간 더 좋을 것이라고 생각합니다.

    0.5*log(x)
    
    

    대칭

    복소수는 i와 -i를 구별 할 수 없기 때문에 대칭을 갖습니다 ( '올바른'기호에 빼기 부호를 뒀다는 것을 보여주는 방법은 없습니다. i--i 및 -i-i))

    따라서 픽셀의 색상을 계산할 때마다 실수 라인의 반대쪽에있는 다른 픽셀의 색상도 계산했습니다. 해당 픽셀도 이미지에 있으면 나중에 다시 계산할 필요가 없습니다.

    베일 아웃 테스트

    log(a*b)
    
    

    이 내용은 읽기 어려우며 의견이 도움이되지 않았습니다. Mandelbrot 세트의 많은 부분을 포함하도록 구제 테스트가 개선 될 수 있다면 코드가 더 빠르게 실행됩니다. 그렇지 않으면 모든 지점이 루프를 통해 최대 횟수만큼 실행되기 때문입니다.

    어떻게 작동하는지 모르겠 기 때문에 개선에 대한 제안을 할 수는 없지만 지금 얼마나 커버하는지 확인하려면 코드를 변경하여 구제 점을 다른 색상으로 설정하십시오. 이미지의 다른 곳에 나타나지 않습니다. 커버되지 않은 부품을 보면 개선 방법에 대한 아이디어를 얻거나 그대로 충분하다고 생각할 수 있습니다.

  • 답변 # 5

    log(a) * log(b) 사용  자신의 log2(1/2) 를 정의하는 대신  기능. 제공 한 문자열 외에 시스템 오류 메시지를 자동으로보고하고 -1 에 인쇄합니다.   int brightness = 256.0 * log2(3.75 + iterations - log2(log2(r))) / log2((double)MAX_TESTS); 대신 .

    와이즈 비츠 선호   r 의 외부 범위에서 지역 변수에 대한 전역 변수 . 여전히 캡슐화를 구현하고 전역을 함수에 전달할 수 있습니다. 이미지의 크기는 전체 일 수도 있습니다. 그러나 log2(log2(r))  내부 루프에 로컬이어야합니다.

    마찬가지로, 실제 복잡한 수학은 log2(log2(11,224)) 와는 별도의 함수에 있어야합니다 . 와이즈 비즈  아마도 int brightness = 256.0 * log2(2.0 + iterations) / log2((double)MAX_TESTS); 해야합니다   int brightness = 256.0 * log2(iterations) / log2((double)MAX_TESTS-1); 를 재생성하는 대신 처음부터 숫자  모든 내부 반복과 함께.

    공식 if (r_squared * (8.0 * r_squared - 3.0) < 3.0/32.0 - creal(c))  성능을 희생하지 않고 제곱합을 작성하는 것보다 간단합니다.

    와이즈 비즈 인 것 같아   perror 보다 더 읽기 쉽습니다. . 공식과 비슷하지만 알고리즘과 유사하지 않습니다.

    와이즈 비츠 선호   error 이상  입력의 유효성을 검사하기 때문입니다. 첫 번째 인수가 숫자가 아닌 경우 높이에 대해 불평하는 대신 사용 알림을 인쇄하는 것이 좋습니다.

    니트 픽을 사용하지만 다음과 같이 여러 줄 문자열을 끊을 수 있습니다 :

    stderr
    
    

    그리고

    stdout
    
    

    후자는 아마도 몇 개의 진술 일 것이다.

    static

  • 이전 표준 GPIO 핀으로 릴레이 채널의 16 개 채널 중 10 개를 구동 할 수 있습니까
  • 다음 gpio - 릴레이 보드 연결시 Raspi의 USB 및 이더넷이 때때로 종료 됨