diff --git a/CHANGELOG.md b/CHANGELOG.md index d51397a..863fc4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,22 @@ Notable changes to this project are documented in this file. The format is based Breaking changes: New features: +- Ported various functions & constants from `purescript-math` (#18 by @JamieBallingall) + + Specifically... + - `abs`, `sign` + - `max`, `min` (which work differently than `Number`'s `Ord` instance) + - `ceil`, `floor`, `trunc`, `remainder`/`%`, `round` + - `log` + - `exp`, `pow`, `sqrt` + - `acos`, `asin`, `atan`, `atan2`, `cos`, `sin`, `tan` + - Numeric constants: `e`, `ln2`, `ln10`, `log10e`, `log2e`, `pi`, `sqrt1_2`, + `sqrt2`, and `tau` Bugfixes: Other improvements: +- Removed dependency on `purescript-math` ## [v8.0.0](https://github.com/purescript/purescript-numbers/releases/tag/v7.0.0) - 2021-02-26 diff --git a/README.md b/README.md index c2734aa..a92b363 100644 --- a/README.md +++ b/README.md @@ -12,52 +12,23 @@ Utility functions for working with PureScripts builtin `Number` type. spago install numbers ``` -## Examples - -Parsing: - -```purs -> fromString "12.34" -(Just 12.34) - -> fromString "1e-3" -(Just 0.001) -``` - -Formatting (`Data.Number.Format`): - -```purs -> let x = 1234.56789 - -> toStringWith (precision 6) x -"1234.57" - -> toStringWith (fixed 3) x -"1234.568" - -> toStringWith (exponential 2) x -"1.23e+3" -``` - -Approximate comparisons (`Data.Number.Approximate`): - -```purs -> 0.1 + 0.2 == 0.3 -false - -> 0.1 + 0.2 ≅ 0.3 -true -``` - -_NaN_ and _infinity_: - -```purs -> isNaN (Math.asin 2.0) -true - -> isFinite (1.0 / 0.0) -false -``` +## Scope + +* Parsing with `fromString` +* Formating with `toStringWith`, see `Data.Number.Format` +* Approximate comparisions with `≅`, see `Data.Number.Approximate` +* Not-a-number and infinite value detection with `isNaN` and `isFinite` +* Remainder with `%` +* Trignometric functions with `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, and + `atan2` +* Natural logarithm and exponents with `log` and `exp` +* Powers with `sqrt` and `pow` +* Rounding with `ceil`, `floor`, `round`, and `trunc` +* Numeric minimum and maximum with `min` and `max`, which behave differently to + the versions in `Data.Ord` on values of `NaN` +* Sign and absolute value functions `sign` and `abs` +* Numeric constants `e`, `ln 2`, `ln10`, `log10e`, `log2e`, `pi`, `sqrt1_2`, + `sqrt2`, and `tau` ## Documentation diff --git a/bower.json b/bower.json index e1193e2..8568ea9 100644 --- a/bower.json +++ b/bower.json @@ -17,7 +17,6 @@ ], "dependencies": { "purescript-functions": "master", - "purescript-math": "master", "purescript-maybe": "master" }, "devDependencies": { diff --git a/src/Data/Number.js b/src/Data/Number.js index 73c221c..d100f07 100644 --- a/src/Data/Number.js +++ b/src/Data/Number.js @@ -14,3 +14,67 @@ export function fromStringImpl(str, isFinite, just, nothing) { return nothing; } } + +export const abs = Math.abs; + +export const acos = Math.acos; + +export const asin = Math.asin; + +export const atan = Math.atan; + +export const atan2 = function (y) { + return function (x) { + return Math.atan2(y, x); + }; +}; + +export const ceil = Math.ceil; + +export const cos = Math.cos; + +export const exp = Math.exp; + +export const floor = Math.floor; + +export const log = Math.log; + +export const max = function (n1) { + return function (n2) { + return Math.max(n1, n2); + }; +}; + +export const min = function (n1) { + return function (n2) { + return Math.min(n1, n2); + }; +}; + +export const pow = function (n) { + return function (p) { + return Math.pow(n, p); + }; +}; + +export const remainder = function (n) { + return function (m) { + return n % m; + }; +}; + +export const round = Math.round; + +export const sign = Math.sign ? Math.sign : function(x) { + return x === 0 || x !== x ? x : (x < 0 ? -1 : 1); +}; + +export const sin = Math.sin; + +export const sqrt = Math.sqrt; + +export const tan = Math.tan; + +export const trunc = Math.trunc ? Math.trunc : function(x) { + return x < 0 ? Math.ceil(x) : Math.floor(x); +} diff --git a/src/Data/Number.purs b/src/Data/Number.purs index 3c6a587..926b2e5 100644 --- a/src/Data/Number.purs +++ b/src/Data/Number.purs @@ -5,21 +5,81 @@ module Data.Number , isNaN , infinity , isFinite + , abs + , acos + , asin + , atan + , atan2 + , ceil + , cos + , exp + , floor + , log + , max + , min + , pow + , remainder, (%) + , round + , sign + , sin + , sqrt + , tan + , trunc + , e + , ln2 + , ln10 + , log10e + , log2e + , pi + , sqrt1_2 + , sqrt2 + , tau ) where import Data.Function.Uncurried (Fn4, runFn4) import Data.Maybe (Maybe(..)) --- | Not a number (NaN) +-- | Not a number (NaN). +-- | ```purs +-- | > nan +-- | NaN +-- | ``` foreign import nan :: Number --- | Test whether a number is NaN +-- | Test whether a number is NaN. +-- | ```purs +-- | > isNaN 0.0 +-- | false +-- | +-- | > isNaN nan +-- | true +-- | ``` foreign import isNaN :: Number -> Boolean --- | Positive infinity +-- | Positive infinity. For negative infinity use `(-infinity)` +-- | ```purs +-- | > infinity +-- | Infinity +-- | +-- | > (-infinity) +-- | - Infinity +-- | ``` foreign import infinity :: Number --- | Test whether a number is finite +-- | Test whether a number is finite. +-- | ```purs +-- | > isFinite 0.0 +-- | true +-- | +-- | > isFinite infinity +-- | false +-- | +-- | > isFinite (-infinity) +-- | false +-- | +-- | > isFinite nan +-- | false +-- | ``` foreign import isFinite :: Number -> Boolean -- | Attempt to parse a `Number` using JavaScripts `parseFloat`. Returns @@ -53,3 +113,251 @@ fromString :: String -> Maybe Number fromString str = runFn4 fromStringImpl str isFinite Just Nothing foreign import fromStringImpl :: Fn4 String (Number -> Boolean) (forall a. a -> Maybe a) (forall a. Maybe a) (Maybe Number) + +-- | Returns the absolute value of the argument. +-- | ```purs +-- | > x = -42.0 +-- | > sign x * abs x == x +-- | true +-- | ``` +foreign import abs :: Number -> Number + +-- | Returns the inverse cosine in radians of the argument. +-- | ```purs +-- | > acos 0.0 == pi / 2.0 +-- | true +-- | ``` +foreign import acos :: Number -> Number + +-- | Returns the inverse sine in radians of the argument. +-- | ```purs +-- | > asin 1.0 == pi / 2.0 +-- | true +-- | ``` +foreign import asin :: Number -> Number + +-- | Returns the inverse tangent in radians of the argument. +-- | ```purs +-- | > atan 1.0 == pi / 4.0 +-- | true +-- | ``` +foreign import atan :: Number -> Number + +-- | Four-quadrant tangent inverse. Given the arguments `y` and `x`, returns +-- | the inverse tangent of `y / x`, where the signs of both arguments are used +-- | to determine the sign of the result. +-- | If the first argument is negative, the result will be negative. +-- | The result is the angle between the positive x axis and a point `(x, y)`. +-- | ```purs +-- | > atan2 0.0 1.0 +-- | 0.0 +-- | > atan2 1.0 0.0 == pi / 2.0 +-- | true +-- | ``` +foreign import atan2 :: Number -> Number -> Number + +-- | Returns the smallest integer not smaller than the argument. +-- | ```purs +-- | > ceil 1.5 +-- | 2.0 +-- | ``` +foreign import ceil :: Number -> Number + +-- | Returns the cosine of the argument, where the argument is in radians. +-- | ```purs +-- | > cos (pi / 4.0) == sqrt2 / 2.0 +-- | true +-- | ``` +foreign import cos :: Number -> Number + +-- | Returns `e` exponentiated to the power of the argument. +-- | ```purs +-- | > exp 1.0 +-- | 2.718281828459045 +-- | ``` +foreign import exp :: Number -> Number + +-- | Returns the largest integer not larger than the argument. +-- | ```purs +-- | > floor 1.5 +-- | 1.0 +-- | ``` +foreign import floor :: Number -> Number + +-- | Returns the natural logarithm of a number. +-- | ```purs +-- | > log e +-- | 1.0 +foreign import log :: Number -> Number + +-- | Returns the largest of two numbers. Unlike `max` in Data.Ord this version +-- | returns NaN if either argument is NaN. +foreign import max :: Number -> Number -> Number + +-- | Returns the smallest of two numbers. Unlike `min` in Data.Ord this version +-- | returns NaN if either argument is NaN. +foreign import min :: Number -> Number -> Number + +-- | Return the first argument exponentiated to the power of the second argument. +-- | ```purs +-- | > pow 3.0 2.0 +-- | 9.0 +-- | > sqrt 42.0 == pow 42.0 0.5 +-- | true +-- | ``` + +foreign import pow :: Number -> Number -> Number + +-- | Computes the remainder after division. This is the same as JavaScript's `%` operator. +-- ```purs +-- > 5.3 % 2.0 +-- 1.2999999999999998 +-- ``` +foreign import remainder :: Number -> Number -> Number + +infixl 7 remainder as % + +-- | Returns the integer closest to the argument. +-- | ```purs +-- | > round 1.5 +-- | 2.0 +-- | ``` +foreign import round :: Number -> Number + +-- | Returns either a positive or negative +/- 1, indicating the sign of the +-- | argument. If the argument is 0, it will return a +/- 0. If the argument is +-- | NaN it will return NaN. +-- | ```purs +-- | > x = -42.0 +-- | > sign x * abs x == x +-- | true +-- | ``` +foreign import sign :: Number -> Number + +-- | Returns the sine of the argument, where the argument is in radians. +-- | ```purs +-- | > sin (pi / 2.0) +-- | 1.0 +-- | ``` +foreign import sin :: Number -> Number + +-- | Returns the square root of the argument. +-- | ```purs +-- | > sqrt 49.0 +-- | 7.0 +-- | ``` +foreign import sqrt :: Number -> Number + +-- | Returns the tangent of the argument, where the argument is in radians. +-- | ``` +-- | > tan (pi / 4.0) +-- | 0.9999999999999999 +-- | ``` +foreign import tan :: Number -> Number + +-- | Truncates the decimal portion of a number. Equivalent to `floor` if the +-- | number is positive, and `ceil` if the number is negative. +-- | ```purs +-- | ceil 1.5 +-- | 2.0 +-- | ``` +foreign import trunc :: Number -> Number + +-- | The base of the natural logarithm, also known as Euler's number or *e*. +-- | ```purs +-- | > log e +-- | 1.0 +-- | +-- | > exp 1.0 == e +-- | true +-- | +-- | > e +-- | 2.718281828459045 +-- | ``` +e :: Number +e = 2.718281828459045 + +-- | The natural logarithm of 2. +-- | ```purs +-- | > log 2.0 == ln2 +-- | true +-- | +-- | > ln2 +-- | 0.6931471805599453 +-- | ``` +ln2 :: Number +ln2 = 0.6931471805599453 + +-- | The natural logarithm of 10. +-- | ```purs +-- | > log 10.0 == ln10 +-- | true +-- | +-- | > ln10 +-- | 2.302585092994046 +-- | ``` +ln10 :: Number +ln10 = 2.302585092994046 + +-- | Base 10 logarithm of `e`. +-- | ```purs +-- | > 1.0 / ln10 - log10e +-- | -5.551115123125783e-17 +-- | +-- | > log10e +-- | 0.4342944819032518 +-- | ``` +log10e :: Number +log10e = 0.4342944819032518 + +-- | The base 2 logarithm of `e`. +-- | ```purs +-- | > 1.0 / ln2 == log2e +-- | true +-- | +-- | > log2e +-- | 1.4426950408889634 +-- | ``` +log2e :: Number +log2e = 1.4426950408889634 + +-- | The ratio of the circumference of a circle to its diameter. +-- | ```purs +-- | > pi +-- | 3.141592653589793 +-- | ``` +pi :: Number +pi = 3.141592653589793 + +-- | The square root of one half. +-- | ```purs +-- | > sqrt 0.5 == sqrt1_2 +-- | true +-- | +-- | > sqrt1_2 +-- | 0.7071067811865476 +-- | ``` +sqrt1_2 :: Number +sqrt1_2 = 0.7071067811865476 + +-- | The square root of two. +-- | ```purs +-- | > sqrt 2.0 == sqrt2 +-- | true +-- | +-- | > sqrt2 +-- | 1.4142135623730951 +-- | ``` +sqrt2 :: Number +sqrt2 = 1.4142135623730951 + +-- | The ratio of the circumference of a circle to its radius. +-- | ```purs +-- | > 2.0 * pi == tau +-- | true +-- | +-- | > tau +-- | 6.283185307179586 +-- | ``` +tau :: Number +tau = 6.283185307179586 diff --git a/src/Data/Number/Approximate.purs b/src/Data/Number/Approximate.purs index 0c3494e..b8afcf5 100644 --- a/src/Data/Number/Approximate.purs +++ b/src/Data/Number/Approximate.purs @@ -13,7 +13,7 @@ module Data.Number.Approximate import Prelude -import Math (abs) +import Data.Number (abs) -- | A newtype for (small) numbers, typically in the range *[0:1]*. It is used -- | as an argument for `eqRelative`. @@ -93,4 +93,3 @@ newtype Tolerance = Tolerance Number -- | ``` eqAbsolute :: Tolerance -> Number -> Number -> Boolean eqAbsolute (Tolerance tolerance) x y = abs (x - y) <= tolerance - diff --git a/test/Test/Main.purs b/test/Test/Main.purs index 9c07f5c..98e466d 100644 --- a/test/Test/Main.purs +++ b/test/Test/Main.purs @@ -3,19 +3,19 @@ module Test.Main where import Prelude import Data.Maybe (Maybe(..), fromMaybe) +import Data.Number ((%), abs, acos, asin, atan, atan2, ceil, cos, exp, floor, fromString, infinity, isFinite, isNaN, ln10, ln2, log10e, log2e, nan, pi, pow, round, sign, sin, sqrt, sqrt1_2, sqrt2, tan, tau, trunc) +import Data.Number (log) as Num +import Data.Number.Approximate (Fraction(..), Tolerance(..), eqAbsolute, eqRelative, (≅), (≇)) +import Data.Number.Format (precision, fixed, exponential, toStringWith, toString) import Effect (Effect) import Effect.Console (log) - -import Data.Number (isFinite, infinity,nan, isNaN, fromString) -import Data.Number.Format (precision, fixed, exponential, toStringWith, toString) -import Data.Number.Approximate (Fraction(..), Tolerance(..), eqRelative, eqAbsolute, (≅), (≇)) - import Test.Assert (assert, assertTrue', assertFalse', assertEqual) main :: Effect Unit main = do globalsTestCode numbersTestCode + numericsTestCode -- Test code for the `purescript-globals` repo before its' Number-related -- code was moved into this repo @@ -286,3 +286,199 @@ numbersTestCode = do log "\teqAbsolute (compare against 0)" assertTrue' "should succeed for numbers smaller than the tolerance" $ 0.0 ~= -0.09 + +numericsTestCode :: Effect Unit +numericsTestCode = do + let assertApproxEqual = \x y -> assert (eqAbsolute (Tolerance 1e-12) x y) + let pi_4 = pi / 4.0 + let pi_2 = pi / 2.0 + let neg_pi = -pi + + log "Data.Number.sin" + log " sin 0.0 = 0.0" + sin 0.0 `assertApproxEqual` 0.0 + + log " sin (pi / 4.0) = sqrt1_2" + sin pi_4 `assertApproxEqual` sqrt1_2 + + log " sin (pi / 2.0) = 1.0" + sin pi_2 `assertApproxEqual` 1.0 + + log " sin pi = 0.0" + sin pi `assertApproxEqual` 0.0 + + log " sin tau = 0.0" + sin tau `assertApproxEqual` 0.0 + + log "Data.Number.cos" + log " cos 0.0 = 1.0" + cos 0.0 `assertApproxEqual` 1.0 + + log " cos (pi / 4.0) = sqrt1_2" + cos pi_4 `assertApproxEqual` sqrt1_2 + + log " cos (pi / 2.0) = 0.0" + cos pi_2 `assertApproxEqual` 0.0 + + log " cos pi = -1.0" + cos pi `assertApproxEqual` -1.0 + + log " cos tau = 1.0" + cos tau `assertApproxEqual` 1.0 + + log "Data.Number.tan" + log " tan 0.0 = 0.0" + tan 0.0 `assertApproxEqual` 0.0 + + log " tan (pi / 4.0) = 1.0" + tan pi_4 `assertApproxEqual` 1.0 + + log " tan pi = 0.0" + tan pi `assertApproxEqual` 0.0 + + log " tan tau = 0.0" + tan tau `assertApproxEqual` 0.0 + + log "Data.Number.asin" + log " asin (sin 0.0) = 0.0" + asin (sin 0.0) `assertApproxEqual` 0.0 + + log " asin (sin pi / 4.0) = pi / 4.0" + asin (sin pi_4) `assertApproxEqual` pi_4 + + log " asin (sin pi / 2.0) = pi / 2.0" + asin (sin pi_2) `assertApproxEqual` pi_2 + + log "Data.Number.acos" + log " acos (cos 0.0) = 0.0" + acos (cos 0.0) `assertApproxEqual` 0.0 + + log " acos (cos pi / 4.0) = pi / 4.0" + acos (cos pi_4) `assertApproxEqual` pi_4 + + log " acos (cos pi / 2.0) = pi / 2.0" + acos (cos pi_2) `assertApproxEqual` pi_2 + + log "Data.Number.atan" + log " atan (tan 0.0) = 0.0" + atan (tan 0.0) `assertApproxEqual` 0.0 + + log " atan (tan pi / 4.0) = pi / 4.0" + atan (tan pi_4) `assertApproxEqual` pi_4 + + log " atan (tan pi / 2.0) = pi / 2.0" + atan (tan pi_2) `assertApproxEqual` pi_2 + + log "Data.Number.atan2" + log " atan2 1.0 2.0 = atan (1.0 / 2.0)" + atan2 1.0 2.0 `assertApproxEqual` atan (1.0 / 2.0) + + log "Data.Number.log" + log " log 2.0 = ln2" + Num.log 2.0 `assertApproxEqual` ln2 + + log " log 10.0 = ln10" + Num.log 10.0 `assertApproxEqual` ln10 + + log "Data.Number.exp" + log " exp ln2 = 2.0" + exp ln2 `assertApproxEqual` 2.0 + + log " exp ln10 = 10.0" + exp ln10 `assertApproxEqual` 10.0 + + log "Data.Number.abs" + log " abs pi = pi" + assert $ abs pi == pi + + log " abs (-pi) = pi" + assert $ abs neg_pi == pi + + log "Data.Number.sign" + log " sign pi = 1.0" + assert $ sign pi == 1.0 + + log " sign (-pi) = -1.0" + assert $ sign neg_pi == -1.0 + + log "Data.Number.sqrt" + log " sqrt 2.0 = sqrt2" + sqrt 2.0 `assertApproxEqual` sqrt2 + + log " sqrt (1.0 / 2.0) = sqrt1_2" + sqrt (1.0 / 2.0) `assertApproxEqual` sqrt1_2 + + log "Data.Number.pow" + log " 2.0 `pow` (1.0 / 2.0) = sqrt2" + (2.0 `pow` (1.0 / 2.0)) `assertApproxEqual` sqrt2 + + log "Data.Number.min" + log " min 0.0 1.0 = 0.0" + assert $ min 0.0 1.0 == 0.0 + + log "Data.Number.max" + log " max 0.0 1.0 = 1.0" + assert $ max 0.0 1.0 == 1.0 + + log "Data.Number rounding" + log " ceil 4.7 = 5.0" + assert $ ceil 4.7 == 5.0 + + log " floor 4.7 = 4.0" + assert $ floor 4.7 == 4.0 + + log " round 4.7 = 5.0" + assert $ round 4.7 == 5.0 + + log " trunc 4.7 = 4.0" + assert $ trunc 4.7 == 4.0 + + log " ceil (-4.7) = -4.0" + assert $ ceil (-4.7) == -4.0 + + log " floor (-4.7) = -5.0" + assert $ floor (-4.7) == -5.0 + + log " round (-4.7) = -5.0" + assert $ round (-4.7) == -5.0 + + log " trunc (-4.7) = -4.0" + assert $ trunc (-4.7) == -4.0 + + log " ceil 4.3 = 5.0" + assert $ ceil 4.3 == 5.0 + + log " floor 4.3 = 4.0" + assert $ floor 4.3 == 4.0 + + log " round 4.3 = 4.0" + assert $ round 4.3 == 4.0 + + log " trunc 4.3 = 4.0" + assert $ trunc 4.3 == 4.0 + + log " ceil (-4.3) = -4.0" + assert $ ceil (-4.3) == -4.0 + + log " floor (-4.3) = -5.0" + assert $ floor (-4.3) == -5.0 + + log " round (-4.3) = -4.0" + assert $ round (-4.3) == -4.0 + + log " trunc (-4.3) = -4.0" + assert $ trunc (-4.3) == -4.0 + + log "Data.Number.remainder (%)" + log " 12.0 % 5.0 = 2.0" + assert $ 12.0 % 5.0 == 2.0 + + log " (-12.0) % 5.0 = 2.0" + assert $ (-12.0) % 5.0 == -2.0 + + log "Data.Number constants" + log " log10e = 1.0 / ln10" + log10e `assertApproxEqual` (1.0 / ln10) + + log " log2e = 1.0 / ln2" + log2e `assertApproxEqual` (1.0 / ln2)