art-standard-lib
Version:
The Standard Library for JavaScript that aught to be.
195 lines (155 loc) • 6.59 kB
text/coffeescript
RegExpExtensions = require './RegExpExtensions'
{numberRegexp} = RegExpExtensions
# Picked smallest simple number > EPSILON for single an double-precision numbers
# that passes test suite.
float64Precision = 4e-16 # > 52 bits: 1 / 2**52 == 2.220446049250313e-16
float32Precision = 4e-7 # > 23 bits: 1 / 2**23 == 1.1920928955078125e-7
onePlusFloat64Precision = 1 + float64Precision
onePlusFloat32Precision = 1 + float32Precision
inverseFloat64Precision = 1 / float64Precision
inverstFloat32Precision = 1 / float32Precision
{abs, min, max, ceil, floor, round, random, pow} = self.Math
Math.log2 ?= (x) -> Math.log(x) / Math.LOG2E
log10 = Math.log10 ?= (x) -> Math.log(x) / Math.log 10
module.exports = class MathExtensions
@nearInfinity: pow 10, 100
@nearInfinityResult: pow 10, 50
@float32Precision: float32Precision
@float64Precision: float64Precision
@modulo: (a, b) -> r = a % b; if r < 0 then r + b else r
@stringToNumberArray: (string) ->
a = string.split ","
for v, i in a
match = v.match(numberRegexp)
a[i] = if match? then match[0] - 0 else 0
a
@numberToTightString: (n, decimalPrecision = 16) ->
v = n.toPrecision decimalPrecision
if /e/.test v
v.replace /([0-9]+(\.[0-9]+[1-9])?)\.?0+e/, "$1e"
else
v.replace /([0-9]+(\.[0-9]+[1-9])?)\.?0+$/, "$1"
# .replace /\.0+($|e)/, "$1"
@minMagnitude: (a, magnitude) ->
if a < 0
min a, -magnitude
else
max a, magnitude
@maxMagnitude: (a, magnitude) -> bound -magnitude, a, magnitude
@maxChange: (newValue, oldValue, maxChangeV) ->
bound oldValue - maxChangeV, newValue, oldValue + maxChangeV
@bound: bound = (a, b, c) ->
return a if isNaN b
if b < a then a
else if b > c then c
else b
@absGt: (a, b) -> abs(a) > abs b
@absLt: (a, b) -> abs(a) < abs b
@absGte: (a, b) -> abs(a) >= abs b
@absLte: (a, b) -> abs(a) <= abs b
@abs: abs
@min: min #(a, b, c) -> if c? and c <= a and c <= b then c else if a <= b then a else b
@max: max #(a, b, c) -> if c? and c >= a and c >= b then c else if a >= b then a else b
# @round: round
@ceil: (v, m = 1) -> ceil(v / m) * m
@floor: (v, m = 1) -> floor(v / m) * m
@round: (v, m = 1) -> round(v / m) * m
@simplifyNum: (num) -> round(num * inverseFloat64Precision) * float64Precision
# See float32Eq for notes
@floatEq: floatEq = (n1, n2) ->
if n1 == n2 || abs(n1 - n2) < float64Precision
true
else if n1 * n2 > 0
n1 = abs n1
n2 = abs n2
(n1 * onePlusFloat64Precision > n2) && (n2 * onePlusFloat64Precision > n1)
else
false
@floatGte: (a, b) -> a >= b || floatEq a, b
@floatLte: (a, b) -> a <= b || floatEq a, b
@floatGt: (a, b) -> a > b && !floatEq a, b
@floatLt: (a, b) -> a < b && !floatEq a, b
###
WARNING: if you are working with very small, near-zero numbers, and
don't want them be be considered actually 0, don't use this!
OUT:
true if two floating point numbers are within
'floating-point error' of each other.
What does that mean?
For exponents > 0
They are the same if their exponent is the same and their mantisssas
are very close.
For exponents < 0
HOWEVER, negative-exponent numbers are compared using their
full value. That means theit exponents could be very different.
return true iff abs(a - b) < float32Precision
NOTES
The problem is comparing against 0. Since "0" has no magnitude, we
have to define how we compare when one of the two numbers is 0 and
the other isn't.
Option 1: always not-equal
Option 2: equal if the mantissa is near-zero
Option 3: equal if the value, including exponent, is near-zero
i.e. - use float32Eq0
I've basically chosen Option #3.
To maintain maximum consistency, I've decided ALL numbers with
exponents < 0 will be compared without compensating for their magnitudes.
###
@float32Eq: float32Eq = (n1, n2) ->
# handle exact equality fast
if n1 == n2 || abs(n1 - n2) < float32Precision
true
else
n1 = Math.abs n1
n2 = Math.abs n2
(n1 * onePlusFloat32Precision > n2) && (n2 * onePlusFloat32Precision > n1)
@float32Gte: (a, b) -> a >= b || float32Eq a, b
@float32Lte: (a, b) -> a <= b || float32Eq a, b
@float32Gt: (a, b) -> a > b && !float32Eq a, b
@float32Lt: (a, b) -> a < b && !float32Eq a, b
@floatEq0: floatEq0 = (n) -> n == 0 || float64Precision > abs n
@float32Eq0: float32Eq0 = (n) -> n == 0 || float32Precision > abs n
# Purpose: if n is floatEq0, make it actually 0, otherwise leave it alone.
@floatTrue0: (n) -> if n == 0 || float64Precision > abs n then 0 else n
@float32True0: (n) -> if n == 0 || float32Precision > abs n then 0 else n
# return intenger between [0, max)
@random: random
@intRand: (max) -> random() * max | 0
@boolRand: -> random() < .5
@iPart: (v) -> v - (v % 1)
@fPart: (v) -> v % 1
@commaize: (x) -> x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
# use: a predictable random function for testing
# ex: Math.cyclingSequenceFunction [0.3206, 0.9062, 0.9956, 0.7878]
@cyclingSequenceFunction: (sequence) ->
sequencePos = sequence.length
->
sequencePos++
sequencePos = 0 if sequencePos >= sequence.length
sequence[sequencePos]
# https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript
# module.exports = function Alea(seed) {
# if(seed == undefined) {seed = +new Date() + Math.random();}
# function Mash() {
# var n = 4022871197;
# return function(r) {
# for(var t, s, u = 0, e = 0.02519603282416938; u < r.length; u++)
# s = r.charCodeAt(u), f = (e * (n += s) - (n*e|0)),
# n = 4294967296 * ((t = f * (e*n|0)) - (t|0)) + (t|0);
# return (n|0) * 2.3283064365386963e-10;
# }
# }
# return function() {
# var m = Mash(), a = m(" "), b = m(" "), c = m(" "), x = 1, y;
# seed = seed.toString(), a -= m(seed), b -= m(seed), c -= m(seed);
# a < 0 && a++, b < 0 && b++, c < 0 && c++;
# return function() {
# var y = x * 2.3283064365386963e-10 + a * 2091639; a = b, b = c;
# return c = y - (x = y|0);
# };
# }();
# }
@seededRandomNumberGenerator: (seed = Math.random()) ->
_rngSeed = Math.floor 48271 * 48271 * Math.abs(seed) + 1
->
(_rngSeed = _rngSeed * 48271 % 2147483647) / 2147483648