# A little math utility for common operations. Don't expect any high-level # mathematical operations nor godly optimizations expected from a typical math # library, it's just basic high school type of shit in all aspects. { pkgs, lib, self }: rec { # We have the rounding functions here anyways so we may as well include the # rest of the decimal place changing functions here for consistency. inherit (builtins) floor ceil; constants = { pi = 3.141592653589793238462643383279502884197; e = 2.7182818284590452353602874713527; ln10 = 2.302585092994046; ln2 = 0.6931471805599453; # The precision target for our functions that need them. epsilon = pow 0.1 13; }; # TODO: We may need to export these functions as a separate Nix library. /* Given a number, check if it's an even number. Type: isEven :: Int -> Int Example: isEven 10 => true isEven 13 => false */ isEven = x: (builtins.bitAnd x 1) == 0; /* Given a number, check if it's an odd number. Type: isOdd :: Int -> Int Example: isOdd 10 => true isOdd 13 => false */ isOdd = x: !(isEven x); /* Returns the absolute value of the given number. Type: abs :: Int -> Int Example: abs -4 => 4 abs (1 / 5) => 0.2 */ abs = number: if number < 0 then -(number) else number; /* Exponentiates the given base with the exponent. Type: pow :: Int -> Int -> Int Example: pow 2 3 => 8 pow 6 4 => 1296 */ pow = base: exponent: # Just to be a contrarian, I'll just make this as a tail recursive function # instead lol. let absValue = abs exponent; iter = product: counter: maxCount: if counter > maxCount then product else iter (product * base) (counter + 1) maxCount; value = iter 1 1 absValue; in if exponent < 0 then (1 / value) else value; /* Given a number as x, return e^x. Type: exp :: Number -> Number Example: exp 0 => 1 exp 1 => 2.7182818284590452353602874713527 exp -1 => 0.36787944117144233 */ exp = x: pow constants.e x; /* Given a number, find its square root. This method is implemented using Newton's method. Type: sqrt :: Number -> Number Example: sqrt 4 => 2 sqrt 169 => 13 sqrt 12 => 3.464101615 */ sqrt = number: assert lib.assertMsg (number >= 0) "bahaghariLib.math.sqrt: Only positive numbers are allowed"; let # Changing this value can change the result drastically. A value of # 10^-13 for tolerance seems to be the most balanced so far since we are # dealing with floats and should be enough for most cases. tolerance = constants.epsilon; iter = value: let root = 0.5 * (value + (number / value)); in if (abs (root - value) > tolerance) then iter root else value; in iter number; /* Implements the factorial function with the given value. Type: factorial :: Number -> Number Example: factorial 3 => 6 factorial 10 => 3628800 */ factorial = x: assert lib.assertMsg (x >= 0) "bahaghariLib.math.factorial: Given value is not a positive integer"; product (lib.range 1 x); /* Returns a boolean whether the given number is within the given (inclusive) range. Type: isWithinRange :: Number -> Number -> Number -> Bool Example: isWithinRange 30 50 6 => false isWithinRange 0 100 75 => true */ isWithinRange = min: max: number: (lib.max number min) <= (lib.min number max); /* Returns a boolean whether the given number is within the given (exclusive) range. Type: isWithinRange :: Number -> Number -> Number -> Bool Example: isWithinRange 30 50 6 => false isWithinRange 0 100 75 => true */ isWithinRange' = min: max: number: (lib.max number min) < (lib.min number max); /* Given a number, make it grow by given amount of percentage. A value of 100 should make the number doubled. Type: grow :: Number -> Number -> Number Example: grow 4 50.0 => 2 grow 55.5 100 => 111 */ grow = value: number: number + (percentage number value); /* Similar to `grow` but only limits to be within the given (inclusive) range. Type: grow' :: Number -> Number -> Number -> Number Example: grow' 0 255 12 100 => 24 grow' 1 10 5 (-200) => 1 */ grow' = min: max: value: number: self.trivial.clamp min max (grow number value); /* Given a number, return its value by the given percentage. Type: percentage :: Number -> Number -> Number Example: percentage 100.0 4 => 4 percentage 200.0 5 => 10 percentage 55.4 58 => 32.132 percentage 0 24654 => 0 */ percentage = value: number: if value == 0 then 0 else number / (100.0 / value); /* Given a number, round up (or down) its number to the nearest ones place. Type: round :: Number -> Number Example: round 3.5 => 4 round 2.3 => 2 round 2.7 => 3 */ round = round' 0; /* Given a tens place (10 ^ n) and a number, round the nearest integer to its given place. Type: round' :: Number -> Number -> Number Example: # Round the number to the nearest ones. round' 0 5.65 => 6 # Round the number to the nearest tens. round' 1 5.65 => 10 # Round the number to the nearest hundreds. round' 2 5.65 => 0 # Round the number to the nearest tenth. round' (-1) 5.65 => 5.7 */ round' = tens: number: let nearest = pow 10.0 tens; difference = number / nearest; in floor (difference + 0.5) * nearest; /* Given a base and a modulus, returns the value of a modulo operation. Type: mod :: Number -> Number -> Number Example: mod 5 4 => 1 mod 1245 4.5 => 3 mod 19 (-12) => -5 */ mod = base: modulus: remainder ((remainder base modulus) + modulus) modulus; /* Similar to the nixpkgs' `trivial.mod` but retain the decimal values. This is just an approximation from ECMAScript's implementation of the remainder operator. Type: remainder :: Number -> Number -> Number Example: remainder 4.25 2 => 0.25 remainder 1.5 2 => 1.5 remainder 65 5 => 0 remainder (-54) 4 => -2 remainder (-54) (-4) => -2 */ remainder = dividend: divisor: let quotient = dividend / divisor; in dividend - ((floor quotient) * divisor); /* Adds all of the given items on the list starting from a sum of zero. Type: summate :: List[Number] -> Number Example: summate [ 1 2 3 4 ] => 10 */ summate = builtins.foldl' builtins.add 0; /* Multiply all of the given items on the list starting from a product of 1. Type: product :: List[Number] -> Number Example: product [ 1 2 3 4 ] => 24 */ product = builtins.foldl' builtins.mul 1; # The following trigonometric functions is pretty much sourced from the following link. # https://lantian.pub/en/article/modify-computer/nix-trigonometric-math-library-from-zero.lantian/ /* Given a number in radians, return the value applied with a sine function. Type: sin :: Number -> Number Example: sin 10 => -0.5440211108893698 sin (constants.pi / 2) => 1 */ sin = x: let x' = mod (toFloat x) (2 * constants.pi); step = i: (pow (-1) (i - 1)) * product (lib.genList (j: x' / (j + 1)) (i * 2 - 1)); iter = value: counter: let value' = step counter; in if (abs value') < constants.epsilon then value else iter (value' + value) (counter + 1); in if x < 0 then -(sin (-x)) else iter 0 1; /* Given a number in radians, apply the cosine function. Type: cos :: Number -> Number Example: cos 10 => -0.8390715290764524 cos 0 => 1 */ cos = x: sin (0.5 * constants.pi - x); /* Given a number in radians, apply the tan trigonometric function. Type: tan :: Number -> Number Example: tan 0 => 0 tan 10 => 0.6483608274590866 */ tan = x: (sin x) / (cos x); /* Given a number in radians, convert it to degrees. Type: radiansToDegrees :: Number -> Number Example: radiansToDegrees bahaghariLib.math.constants.pi => 180 radiansToDegrees 180 => 10313.240312355 */ radiansToDegrees = x: x * 180.0 / constants.pi; /* Given a number in degrees unit, convert it to radians. Type: degreesToRadians :: Number -> Number Example: degreesToRadians 180 => 3.141592653589793238462643383279502884197 degreesToRadians 360 => 6.283185307 degreesToRadians 95 => 1.658062789 */ degreesToRadians = x: x * constants.pi / 180.0; }