bahaghari/lib: add trigonometric functions to math subset

Code taken from
https://lantian.pub/en/article/modify-computer/nix-trigonometric-math-library-from-zero.lantian/
with proper attributions (hopefully).
This commit is contained in:
Gabriel Arazas 2024-07-07 20:14:41 +08:00
parent 4d32203d4d
commit e3122bfd24
No known key found for this signature in database
GPG Key ID: 62104B43D00AA360
3 changed files with 164 additions and 3 deletions

View File

@ -1,5 +1,9 @@
Copyright (c) 2024 Gabriel Arazas <foodogsquared@foodogsquared.one>
Program uses the following files/implementations from other authors;
see their respective license headers for more details:
lib/math.nix, specifically the sin, cos, tan, and arctan functions: Copyright (c) 2023 Yuhui Xu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights

View File

@ -14,8 +14,8 @@ rec {
ln10 = 2.302585092994046;
ln2 = 0.6931471805599453;
# The minimum precision for our functions that need them.
epsilon = pow 10 (-13);
# The precision target for our functions that need them.
epsilon = pow 0.1 13;
};
/* Returns the absolute value of the given number.
@ -32,6 +32,20 @@ rec {
abs = number:
if number < 0 then -(number) else number;
/* Given a Nix number, force it to be a floating value.
Type: toFloat :: Number -> Float
Example:
toFloat 5
=> 5.0
toFloat 59.0
=> 59.0
*/
toFloat = x:
1.0 * x;
/* Exponentiates the given base with the exponent.
Type: pow :: Int -> Int -> Int
@ -314,6 +328,60 @@ rec {
*/
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

View File

@ -208,7 +208,7 @@ lib.runTests {
};
testMathSqrt = {
expr = self.math.sqrt 4;
expr = round' (self.math.sqrt 4);
expected = 2;
};
@ -362,4 +362,93 @@ lib.runTests {
expr = self.math.round' (-3) (self.math.radiansToDegrees 4.5);
expected = 257.831;
};
# At this point, most of the things are just adjusting to the quirks of those
# accursed floating-values.
testMathSine = {
expr = round' (self.math.sin 10);
expected = round' (-0.5440211108893698);
};
testMathSine2 = {
expr = self.math.sin 0;
expected = 0;
};
testMathSine3 = let
round' = self.math.round' (-5);
in {
expr = round' (self.math.sin (self.math.constants.pi / 2));
expected = round' 1;
};
testMathSine4 = {
expr = round' (self.math.sin 360);
expected = round' 0.9589157234143065;
};
testMathSine5 = {
expr = round' (self.math.sin 152);
expected = round' 0.933320523748862;
};
testMathSine6 = {
expr = round' (self.math.sin (-152));
expected = round' (-0.933320523748862);
};
testMathCosine = {
expr = round' (self.math.cos 10);
expected = round' (-0.8390715290764524);
};
testMathCosine2 = {
expr = round' (self.math.cos 0);
expected = 1;
};
testMathCosine3 = {
expr = round' (self.math.cos self.math.constants.pi);
expected = -1;
};
testMathCosine4 = {
expr = round' (self.math.cos (self.math.constants.pi * 2));
expected = 1;
};
testMathCosine5 = {
expr = round' (self.math.cos 1);
expected = round' 0.5403023058681398;
};
testMathCosine6 = {
expr = round' (self.math.cos 152);
expected = round' 0.35904428689111606;
};
testMathTangent = {
expr = round' (self.math.tan 10);
expected = round' 0.6483608274590866;
};
testMathTangent2 = {
expr = round' (self.math.tan 0);
expected = 0;
};
testMathTangent3 = {
expr = round' (self.math.tan (self.math.constants.pi / 4));
expected = round' (0.99999999999999999);
};
testMathTangent4 = {
expr = round' (self.math.tan 152);
expected = round' 2.5994579438382797;
};
testMathTangent5 = {
expr = round' (self.math.tan (-152));
expected = round' (-2.5994579438382797);
};
}