💾 Archived View for thrig.me › blog › 2022 › 10 › 08 › slope-equation.gmi captured on 2023-04-19 at 22:58:34. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-01-29)

➡️ Next capture (2024-02-05)

🚧 View Differences

-=-=-=-=-=-=-

The Slope Equation

y=mx+b and m=(y2-y1)/(x2-x1) appear quite often in code I write; in music generation one might have some function or black box producing who knows what numbers; the slope equation can restrict the output (possibly with clamping) to some range: a scale, the MIDI pitch numbers, etc. In roguelike map generation one may also need a linear conversion, perhaps of a sine function to a limited set of integers that become various map characters perhaps representing deep water, water, ground, forest, and mountains. Or, population centers might be indicated by a circle of up to some maximum size; with the minimum and maximum population (or a guess at the maximum, if one is using some complicated generator) one then can work out the math by hand, but it might be nice to generate a function that performs the math for you.

    #!/usr/bin/env perl
    use 5.32.0;
    use warnings;
    use experimental 'signatures';
    
    sub slopeinator ( $x1, $x2, $y1, $y2 ) {
        my $m = ( $y2 - $y1 ) / ( $x2 - $x1 );
        my $i = $y2 - $m * $x2;
        sub ($x) { $m * $x + $i };
    }
    
    my $equation = slopeinator( 1, 20, 8, 64 );
    
    for my $x ( 0, 1, 2, 10, 19, 20, 21 ) {
        say join ' ', $x, $equation->($x);
    }

Note that this is not clamped; -9999 as input could produce something unusable in the output. Clamp can be found in standard libraries (Alexandria for Common LISP) though is otherwise not difficult to write: min, max, given value, and some logic for which of those to return. Another option is to throw an error should a value land out of range, then study where that value came from. This may be difficult if the black box is some AI thing doing who knows what. Another is to run the generator function lots of times and see what properties of the output has (min, max, five-number, etc) and from that determine the minimum and maximum x1 and x2 and whether clamping is required. This is especially useful for complicated generators or generators that include random noise or otherwise rare events.

A clamped version is not much more difficult; if y1 and y2 represent the minimum and maximum those could be clamped on directly in the generated subroutine, here in Common LISP.

    (defun make-clamped-slope (x1 x2 y1 y2)
      (let* ((m (/ (- y2 y1) (- x2 x1))) (b (- y1 (* m x1))))
        (lambda (x)
          (let ((y (+ (* m x) b)))
            (if (> y y2) y2 (if (< y y1) y1 y))))))

    (let ((equation (make-clamped-slope 1 20 8 64)))
      (dolist (x '(0 1 2 10 19 20 21))
        (format t "~&~D -> ~F" x (funcall equation x))))

The two different language implementations help ensure that no one language is buggy, or at least that both are buggy in the same way. First implemetations may also be bad that a second look at the problem in a different language can help resolve. Checking the results against something worked out by hand is also a good step to take, especially if the code will end up in a hopefully tested library used in lots of different places. Also humans are really good at not getting the equations nor input exactly right... did you spot the difference, above, in the interface from an alternative?

If not, consider this interface.

    #include <stdio.h>
    
    struct point {
        double x;
        double y;
    };
    
    struct slope {
        double b;
        double m;
    };
    
    void slope_init(struct slope *factors, struct point *a, struct point *b);
    
    void slope_init(struct slope *factors, struct point *a, struct point *b)
    {
        factors->m = (b->y - a->y) / (b->x - a->x);
        factors->b = b->y - factors->m * b->x;
    }
    
    double slope_calc(struct slope *factors, double x);
    
    double
    slope_calc(struct slope *factors, double x)
    {
        return factors->m * x + factors->b;
    }
    
    int
    main(void)
    {
        struct slope equation;
        slope_init(&equation, &(struct point){.x = 1.0, .y = 8.0},
                   &(struct point){.x = 20.0, .y = 64.0});
        for (int i = 0; i <= 2; i++) {
            printf("%d -> %.2f\n", i, slope_calc(&equation, (double) i));
        }
        for (int i = 19; i <= 21; i++) {
            printf("%d -> %.2f\n", i, slope_calc(&equation, (double) i));
        }
        return 0;
    }

tags #perl #lisp #c

bphflog links

bphflog index

next: Hierarchy