Repository: char0n/ramda-adjunct
Pull request #821 (2019-02-09 10:49:09)

feat: add trunc
Ref #748




Patch:
diff --git a/src/ceil.js b/src/ceil.js
index 90effd2664..e15c766d4f 100644
--- a/src/ceil.js
+++ b/src/ceil.js
@@ -11,8 +11,8 @@ import curry1 from 'ramda/src/internal/_curry1';
  * @since {@link https://char0n.github.io/ramda-adjunct/2.15.0|v2.15.0}
  * @category Math
  * @sig Number -> Number
- * @param {*} number The number to ceil
- * @return {number} The smallest integer greater than or equal to the given number.
+ * @param {number} number The number to ceil
+ * @return {number} The smallest integer greater than or equal to the given number
  * @example
  *
  * RA.ceil(.95); //=> 1
diff --git a/src/index.d.ts b/src/index.d.ts
index 0535949210..f98404e8dd 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -1074,6 +1074,12 @@ declare namespace RamdaAdjunct {
          */
         floor(val: number): number
 
+        /**
+         * Returns the integer part of a number by removing any fractional digits.
+         *
+         */
+        trunc(val: number): number;
+
         /**
          * Identity type.
          */
diff --git a/src/index.js b/src/index.js
index 08adc13303..ac77424aad 100644
--- a/src/index.js
+++ b/src/index.js
@@ -166,5 +166,6 @@ export { default as dropArgs } from './dropArgs';
 export { default as round } from './round';
 export { default as ceil } from './ceil';
 export { default as floor } from './floor';
+export { default as trunc } from './trunc';
 // Types
 export { default as Identity } from './fantasy-land/Identity';
diff --git a/src/internal/polyfills/Math.trunc.js b/src/internal/polyfills/Math.trunc.js
new file mode 100644
index 0000000000..52c62321d6
--- /dev/null
+++ b/src/internal/polyfills/Math.trunc.js
@@ -0,0 +1,14 @@
+import isFinite from '../../isFinite';
+
+const truncPolyfill = v => {
+  const numV = Number(v);
+
+  if (!isFinite(numV)) {
+    return numV;
+  }
+
+  // eslint-disable-next-line no-nested-ternary
+  return numV - (numV % 1) || (numV < 0 ? -0 : numV === 0 ? numV : 0);
+};
+
+export default truncPolyfill;
diff --git a/src/round.js b/src/round.js
index 395caa1a17..c544dad5e9 100644
--- a/src/round.js
+++ b/src/round.js
@@ -9,7 +9,7 @@ import curry1 from 'ramda/src/internal/_curry1';
  * @since {@link https://char0n.github.io/ramda-adjunct/2.15.0|v2.15.0}
  * @category Math
  * @sig Number -> Number
- * @param {*} number The number to round
+ * @param {number} number The number to round
  * @return {number} The value of the given number rounded to the nearest integer
  * @example
  *
diff --git a/src/trunc.js b/src/trunc.js
new file mode 100644
index 0000000000..7138cf2eeb
--- /dev/null
+++ b/src/trunc.js
@@ -0,0 +1,34 @@
+import { bind } from 'ramda';
+import curry1 from 'ramda/src/internal/_curry1';
+
+import polyfill from './internal/polyfills/Math.trunc';
+import isFunction from './isFunction';
+
+export const truncPolyfill = curry1(polyfill);
+
+/**
+ * Returns the integer part of a number by removing any fractional digits.
+ *
+ * @func trunc
+ * @memberOf RA
+ * @since {@link https://char0n.github.io/ramda-adjunct/2.15.0|v2.15.0}
+ * @category Math
+ * @sig Number | String -> Number
+ * @param {number|string} number The number to trunc
+ * @return {number} The integer part of the given number
+ * @example
+ *
+ * RA.trunc(13.37); //=> 13
+ * RA.trunc(42.84); //=> 42
+ * RA.trunc(0.123); //=>  0
+ * RA.trunc(-0.123); //=> -0
+ * RA.trunc('-1.123'); //=> -1
+ * RA.trunc(NaN); //=> NaN
+ * RA.trunc('foo'); //=> NaN
+ */
+
+const trunc = isFunction(Math.trunc)
+  ? curry1(bind(Math.trunc, Math))
+  : truncPolyfill;
+
+export default trunc;


Test patch:
diff --git a/test/trunc.js b/test/trunc.js
new file mode 100644
index 0000000000..2aa47ff11f
--- /dev/null
+++ b/test/trunc.js
@@ -0,0 +1,169 @@
+import * as R from 'ramda';
+import { assert } from 'chai';
+
+import * as RA from '../src';
+import eq from './shared/eq';
+import Symbol from './shared/Symbol';
+import { truncPolyfill } from '../src/trunc';
+
+describe('trunc', function() {
+  it('should truncate fractional digits', function() {
+    eq(RA.trunc(13.37), 13);
+    eq(RA.trunc(42.84), 42);
+    eq(RA.trunc(0.123), 0);
+    eq(RA.trunc(-0.123), -0);
+  });
+
+  context('given number in string', function() {
+    specify('should truncate fractional digits', function() {
+      eq(RA.trunc('-1.123'), -1);
+    });
+  });
+
+  context('given integer number', function() {
+    specify('should return original integer number', function() {
+      eq(RA.trunc(0), 0);
+      eq(RA.trunc(1), 1);
+      eq(RA.trunc(-1), -1);
+    });
+  });
+
+  context('given value that is not a number', function() {
+    specify('should return NaN', function() {
+      eq(RA.trunc(undefined), NaN);
+      eq(RA.trunc(NaN), NaN);
+      eq(RA.trunc({}), NaN);
+      eq(RA.trunc(/a/), NaN);
+      eq(RA.trunc('test'), NaN);
+      eq(RA.trunc(new Error()), NaN);
+    });
+  });
+
+  context('given null', function() {
+    specify('should return 0', function() {
+      eq(RA.trunc(null), 0);
+    });
+  });
+
+  context('given Symbol value', function() {
+    specify('should throw TypeError', function() {
+      let shouldThrow;
+      try {
+        Math.trunc(Symbol(''));
+        shouldThrow = false;
+      } catch (e) {
+        shouldThrow = true;
+      }
+
+      if (shouldThrow) {
+        assert.throw(() => RA.trunc(Symbol('')));
+      }
+    });
+  });
+
+  context('given valid date object', function() {
+    specify(
+      'should return the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC',
+      function() {
+        eq(RA.trunc(new Date(2)), 2);
+      }
+    );
+  });
+
+  context('given Infinity value', function() {
+    specify('should return untouched Infinity value', function() {
+      eq(RA.trunc(Infinity), Infinity);
+      eq(RA.trunc(-Infinity), -Infinity);
+    });
+  });
+
+  it('should support placeholder to specify "gaps"', function() {
+    const trunc = RA.trunc(R.__);
+
+    eq(trunc(0.9), 0);
+  });
+
+  context('truncPolyfill', function() {
+    beforeEach(function() {
+      if (RA.isNotFunction(Math.trunc)) {
+        this.skip();
+      }
+    });
+
+    specify('should truncate fractional digits', function() {
+      eq(truncPolyfill(13.37), 13);
+      eq(truncPolyfill(42.84), 42);
+      eq(truncPolyfill(0.123), 0);
+      eq(truncPolyfill(-0.123), -0);
+    });
+
+    context('given number in string', function() {
+      specify('should truncate fractional digits', function() {
+        eq(truncPolyfill('-1.123'), -1);
+      });
+    });
+
+    context('given integer number', function() {
+      specify('should return original integer number', function() {
+        eq(truncPolyfill(0), 0);
+        eq(truncPolyfill(1), 1);
+        eq(truncPolyfill(-1), -1);
+      });
+    });
+
+    context('given value that is not a number', function() {
+      specify('should return NaN', function() {
+        eq(truncPolyfill(undefined), NaN);
+        eq(truncPolyfill(NaN), NaN);
+        eq(truncPolyfill({}), NaN);
+        eq(truncPolyfill(/a/), NaN);
+        eq(truncPolyfill('test'), NaN);
+        eq(truncPolyfill(new Error()), NaN);
+      });
+    });
+
+    context('given null', function() {
+      specify('should return 0', function() {
+        eq(truncPolyfill(null), 0);
+      });
+    });
+
+    context('given Symbol value', function() {
+      specify('should throw TypeError', function() {
+        let shouldThrow;
+        try {
+          Math.trunc(Symbol(''));
+          shouldThrow = false;
+        } catch (e) {
+          shouldThrow = true;
+        }
+
+        if (shouldThrow) {
+          assert.throw(() => truncPolyfill(Symbol('')));
+        }
+      });
+    });
+
+    context('given valid date object', function() {
+      specify(
+        'should return the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC',
+        function() {
+          eq(truncPolyfill(new Date(2)), 2);
+        }
+      );
+    });
+
+    context('given Infinity value', function() {
+      specify('should return untouched Infinity value', function() {
+        eq(truncPolyfill(Infinity), Infinity);
+        eq(truncPolyfill(-Infinity), -Infinity);
+      });
+    });
+
+    specify('should support placeholder to specify "gaps"', function() {
+      const trunc = truncPolyfill(R.__);
+
+      eq(trunc(0.9), 0);
+    });
+  });
+});