From a28bd044db2078775f1cef503790e08328203586 Mon Sep 17 00:00:00 2001 From: hazrid93 Date: Mon, 20 Jul 2026 10:42:15 +0000 Subject: [PATCH] feat: add a Round function Closes #61. Add round(value, precision=0) mirroring lodash's round: rounds value to precision decimal places. Uses the exponential notation string trick to avoid the common floating point error (e.g. round(1.005, 2) is 1.01, not 1). No arg precision rounds to an integer. --- cdn/radash.esm.js | 5 ++++- cdn/radash.js | 4 ++++ cdn/radash.min.js | 2 +- src/index.ts | 2 +- src/number.ts | 9 +++++++++ src/tests/number.test.ts | 16 ++++++++++++++++ 6 files changed, 35 insertions(+), 3 deletions(-) diff --git a/cdn/radash.esm.js b/cdn/radash.esm.js index 725ed2cd..231b2ef3 100644 --- a/cdn/radash.esm.js +++ b/cdn/radash.esm.js @@ -614,6 +614,9 @@ const toInt = (value, defaultValue) => { const result = parseInt(value); return isNaN(result) ? def : result; }; +function round(value, precision = 0) { + return Number(`${Math.round(Number(`${value}e+${precision}`))}e-${precision}`); +} const shake = (obj, filter = (x) => x === void 0) => { if (!obj) @@ -937,4 +940,4 @@ const trim = (str, charsToTrim = " ") => { return str.replace(regex, ""); }; -export { all, alphabetical, assign, boil, callable, camel, capitalize, chain, clone, cluster, compose, construct, counting, crush, dash, debounce, defer, diff, draw, first, flat, fork, get, group, guard, inRange, intersects, invert, isArray, isDate, isEmpty, isEqual, isFloat, isFunction, isInt, isNumber, isObject, isPrimitive, isPromise, isString, isSymbol, iterate, keys, last, list, listify, lowerize, map, mapEntries, mapKeys, mapValues, max, memo, merge, min, objectify, omit, parallel, partial, partob, pascal, pick, proxied, random, range, reduce, replace, replaceOrAppend, retry, select, series, set, shake, shift, shuffle, sift, sleep, snake, sort, sum, template, throttle, title, toFloat, toInt, toggle, trim, tryit as try, tryit, uid, unique, upperize, zip, zipToObject }; +export { all, alphabetical, assign, boil, callable, camel, capitalize, chain, clone, cluster, compose, construct, counting, crush, dash, debounce, defer, diff, draw, first, flat, fork, get, group, guard, inRange, intersects, invert, isArray, isDate, isEmpty, isEqual, isFloat, isFunction, isInt, isNumber, isObject, isPrimitive, isPromise, isString, isSymbol, iterate, keys, last, list, listify, lowerize, map, mapEntries, mapKeys, mapValues, max, memo, merge, min, objectify, omit, parallel, partial, partob, pascal, pick, proxied, random, range, reduce, replace, replaceOrAppend, retry, round, select, series, set, shake, shift, shuffle, sift, sleep, snake, sort, sum, template, throttle, title, toFloat, toInt, toggle, trim, tryit as try, tryit, uid, unique, upperize, zip, zipToObject }; diff --git a/cdn/radash.js b/cdn/radash.js index 2cbdae65..e8f7fdce 100644 --- a/cdn/radash.js +++ b/cdn/radash.js @@ -617,6 +617,9 @@ var radash = (function (exports) { const result = parseInt(value); return isNaN(result) ? def : result; }; + function round(value, precision = 0) { + return Number(`${Math.round(Number(`${value}e+${precision}`))}e-${precision}`); + } const shake = (obj, filter = (x) => x === void 0) => { if (!obj) @@ -1009,6 +1012,7 @@ var radash = (function (exports) { exports.replace = replace; exports.replaceOrAppend = replaceOrAppend; exports.retry = retry; + exports.round = round; exports.select = select; exports.series = series; exports.set = set; diff --git a/cdn/radash.min.js b/cdn/radash.min.js index 1d131ad1..b206a0b8 100644 --- a/cdn/radash.min.js +++ b/cdn/radash.min.js @@ -1 +1 @@ -var radash=function(i){"use strict";const E=t=>!!t&&t.constructor===Symbol,w=Array.isArray,k=t=>!!t&&t.constructor===Object,N=t=>t==null||typeof t!="object"&&typeof t!="function",y=t=>!!(t&&t.constructor&&t.call&&t.apply),U=t=>typeof t=="string"||t instanceof String,K=t=>h(t)&&t%1===0,W=t=>h(t)&&t%1!==0,h=t=>{try{return Number(t)===t}catch{return!1}},T=t=>Object.prototype.toString.call(t)==="[object Date]",S=t=>!(!t||!t.then||!y(t.then)),J=t=>{if(t===!0||t===!1||t==null)return!0;if(h(t))return t===0;if(T(t))return isNaN(t.getTime());if(y(t)||E(t))return!1;const e=t.length;if(h(e))return e===0;const n=t.size;return h(n)?n===0:Object.keys(t).length===0},j=(t,e)=>{if(Object.is(t,e))return!0;if(t instanceof Date&&e instanceof Date)return t.getTime()===e.getTime();if(t instanceof RegExp&&e instanceof RegExp)return t.toString()===e.toString();if(typeof t!="object"||t===null||typeof e!="object"||e===null)return!1;const n=Reflect.ownKeys(t),r=Reflect.ownKeys(e);if(n.length!==r.length)return!1;for(let s=0;st.reduce((n,r)=>{const s=e(r);return n[s]||(n[s]=[]),n[s].push(r),n},{});function Y(...t){return!t||!t.length?[]:new Array(Math.max(...t.map(({length:e})=>e))).fill([]).map((e,n)=>t.map(r=>r[n]))}function H(t,e){if(!t||!t.length)return{};const n=y(e)?e:w(e)?(r,s)=>e[s]:(r,s)=>e;return t.reduce((r,s,u)=>(r[s]=n(s,u),r),{})}const O=(t,e)=>!t||(t.length??0)===0?null:t.reduce(e);function Q(t,e){return(t||[]).reduce((n,r)=>n+(e?e(r):r),0)}const V=(t,e=void 0)=>t?.length>0?t[0]:e,G=(t,e=void 0)=>t?.length>0?t[t.length-1]:e,z=(t,e,n=!1)=>{if(!t)return[];const r=(u,c)=>e(u)-e(c),s=(u,c)=>e(c)-e(u);return t.slice().sort(n===!0?s:r)},x=(t,e,n="asc")=>{if(!t)return[];const r=(u,c)=>`${e(u)}`.localeCompare(e(c)),s=(u,c)=>`${e(c)}`.localeCompare(e(u));return t.slice().sort(n==="desc"?s:r)},tt=(t,e)=>t?t.reduce((n,r)=>{const s=e(r);return n[s]=(n[s]??0)+1,n},{}):{},et=(t,e,n)=>{if(!t)return[];if(e===void 0)return[...t];for(let r=0;rr)=>t.reduce((r,s)=>(r[e(s)]=n(s),r),{}),nt=(t,e,n)=>t?t.reduce((r,s,u)=>(n(s,u)&&r.push(e(s,u)),r),[]):[];function rt(t,e){const n=e??(r=>r);return O(t,(r,s)=>n(r)>n(s)?r:s)}function st(t,e){const n=e??(r=>r);return O(t,(r,s)=>n(r){const n=Math.ceil(t.length/e);return new Array(n).fill(null).map((r,s)=>t.slice(s*e,s*e+e))},ut=(t,e)=>{const n=t.reduce((r,s)=>{const u=e?e(s):s;return r[u]||(r[u]=s),r},{});return Object.values(n)};function*A(t,e,n=s=>s,r=1){const s=y(n)?n:()=>n,u=e?t:0,c=e??t;for(let o=u;o<=c&&(yield s(o),!(o+r>c));o+=r);}const $=(t,e,n,r)=>Array.from(A(t,e,n,r)),ct=t=>t.reduce((e,n)=>(e.push(...n),e),[]),ot=(t,e,n)=>{if(!t||!e)return!1;const r=n??(u=>u),s=e.reduce((u,c)=>(u[r(c)]=!0,u),{});return t.some(u=>s[r(u)])},R=(t,e)=>t?t.reduce((n,r)=>{const[s,u]=n;return e(r)?[[...s,r],u]:[s,[...u,r]]},[[],[]]):[[],[]],ft=(t,e,n)=>!e&&!t?[]:e?t?n?t.reduce((r,s)=>{const u=e.find(c=>n(s)===n(c));return u?r.push(u):r.push(s),r},[]):t:[]:t,lt=(t,e,n)=>{if(!t&&!e)return[];if(!e)return[...t];if(!t)return[e];for(let r=0;r{if(!t&&!e)return[];if(!t)return[e];if(!e)return[...t];const s=n?(o,a)=>n(o,a)===n(e,a):o=>o===e;return t.find(s)?t.filter((o,a)=>!s(o,a)):(r?.strategy??"append")==="append"?[...t,e]:[e,...t]},dt=t=>t?.filter(e=>!!e)??[],B=(t,e,n)=>{let r=n;for(let s=1;s<=t;s++)r=e(r,s);return r},gt=(t,e,n=r=>r)=>{if(!t?.length&&!e?.length)return[];if(t?.length===void 0)return[...e];if(!e?.length)return[...t];const r=e.reduce((s,u)=>(s[n(u)]=!0,s),{});return t.filter(s=>!r[n(s)])};function ht(t,e){if(t.length===0)return t;const n=e%t.length;return n===0?t:[...t.slice(-n,t.length),...t.slice(0,-n)]}const mt=async(t,e,n)=>{const r=n!==void 0;if(!r&&t?.length<1)throw new Error("Cannot reduce empty array with no init value");const s=r?t:t.slice(1);let u=r?n:t[0];for(const[c,o]of s.entries())u=await e(u,o,c);return u},wt=async(t,e)=>{if(!t)return[];let n=[],r=0;for(const s of t){const u=await e(s,r++);n.push(u)}return n},yt=async t=>{const e=[],n=(u,c)=>e.push({fn:u,rethrow:c?.rethrow??!1}),[r,s]=await m(t)(n);for(const{fn:u,rethrow:c}of e){const[o]=await m(u)(r);if(o&&c)throw o}if(r)throw r;return s};class L extends Error{constructor(e=[]){super();const n=e.find(r=>r.name)?.name??"";this.name=`AggregateError(${n}...)`,this.message=`AggregateError with ${e.length} errors`,this.stack=e.find(r=>r.stack)?.stack??this.stack,this.errors=e}}const pt=async(t,e,n)=>{const r=e.map((d,b)=>({index:b,item:d})),s=async d=>{const b=[];for(;;){const f=r.pop();if(!f)return d(b);const[l,g]=await m(n)(f.item);b.push({error:l,result:g,index:f.index})}},u=$(1,t).map(()=>new Promise(s)),c=await Promise.all(u),[o,a]=R(z(c.flat(),d=>d.index),d=>!!d.error);if(o.length>0)throw new L(o.map(d=>d.error));return a.map(d=>d.result)};async function bt(t){const e=w(t)?t.map(s=>[null,s]):Object.entries(t),n=await Promise.all(e.map(([s,u])=>u.then(c=>({result:c,exc:null,key:s})).catch(c=>({result:null,exc:c,key:s})))),r=n.filter(s=>s.exc);if(r.length>0)throw new L(r.map(s=>s.exc));return w(t)?n.map(s=>s.result):n.reduce((s,u)=>({...s,[u.key]:u.result}),{})}const kt=async(t,e)=>{const n=t?.times??3,r=t?.delay,s=t?.backoff??null;for(const u of A(1,n)){const[c,o]=await m(e)(a=>{throw{_exited:a}});if(!c)return o;if(c._exited)throw c._exited;if(u===n)throw c;r&&await C(r),s&&await C(s(u))}},C=t=>new Promise(e=>setTimeout(e,t)),m=t=>(...e)=>{try{const n=t(...e);return S(n)?n.then(r=>[void 0,r]).catch(r=>[r,void 0]):[void 0,n]}catch(n){return[n,void 0]}},Ot=(t,e)=>{const n=s=>{if(e&&!e(s))throw s},r=s=>s instanceof Promise;try{const s=t();return r(s)?s.catch(n):s}catch(s){return n(s)}};function At(...t){return(...e)=>t.slice(1).reduce((n,r)=>r(n),t[0](...e))}function $t(...t){return t.reverse().reduce((e,n)=>n(e))}const Ct=(t,...e)=>(...n)=>t(...e,...n),Pt=(t,e)=>n=>t({...e,...n}),_t=t=>new Proxy({},{get:(e,n)=>t(n)}),Et=(t,e,n,r)=>function(...u){const c=n?n(...u):JSON.stringify({args:u}),o=t[c];if(o!==void 0&&(!o.exp||o.exp>new Date().getTime()))return o.value;const a=e(...u);return t[c]={exp:r?new Date().getTime()+r:null,value:a},a},Nt=(t,e={})=>Et({},t,e.key??null,e.ttl??null),Tt=({delay:t},e)=>{let n,r=!0;const s=(...u)=>{r?(clearTimeout(n),n=setTimeout(()=>{r&&e(...u),n=void 0},t)):e(...u)};return s.isPending=()=>n!==void 0,s.cancel=()=>{r=!1},s.flush=(...u)=>e(...u),s},St=({interval:t},e)=>{let n=!0,r;const s=(...u)=>{n&&(e(...u),n=!1,r=setTimeout(()=>{n=!0,r=void 0},t))};return s.isThrottled=()=>r!==void 0,s},jt=(t,e)=>{const n=()=>{};return new Proxy(Object.assign(n,t),{get:(r,s)=>r[s],set:(r,s,u)=>(r[s]=u,!0),apply:(r,s,u)=>e(Object.assign({},r))(...u)})};function zt(t,e,n){return typeof t=="number"&&typeof e=="number"&&(typeof n>"u"||typeof n=="number")?(typeof n>"u"&&(n=e,e=0),t>=Math.min(e,n)&&t{const n=e===void 0?0:e;if(t==null)return n;const r=parseFloat(t);return isNaN(r)?n:r},Rt=(t,e)=>{const n=e===void 0?0:e;if(t==null)return n;const r=parseInt(t);return isNaN(r)?n:r},Bt=(t,e=n=>n===void 0)=>t?Object.keys(t).reduce((r,s)=>(e(t[s])||(r[s]=t[s]),r),{}):{},P=(t,e)=>Object.keys(t).reduce((r,s)=>(r[e(s,t[s])]=t[s],r),{}),Lt=(t,e)=>Object.keys(t).reduce((r,s)=>(r[s]=e(t[s],s),r),{}),Zt=(t,e)=>t?Object.entries(t).reduce((n,[r,s])=>{const[u,c]=e(r,s);return n[u]=c,n},{}):{},Dt=t=>t?Object.keys(t).reduce((n,r)=>(n[t[r]]=r,n),{}):{},Ft=t=>P(t,e=>e.toLowerCase()),qt=t=>P(t,e=>e.toUpperCase()),Z=t=>{if(N(t))return t;if(typeof t=="function")return t.bind({});const e=new t.constructor;return Object.getOwnPropertyNames(t).forEach(n=>{e[n]=t[n]}),e},It=(t,e)=>{if(!t)return[];const n=Object.entries(t);return n.length===0?[]:n.reduce((r,s)=>(r.push(e(s[0],s[1])),r),[])},vt=(t,e)=>t?e.reduce((n,r)=>(Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]),n),{}):{},Ut=(t,e)=>t?!e||e.length===0?t:e.reduce((n,r)=>(delete n[r],n),{...t}):{},D=(t,e,n)=>{const r=e.split(/[\.\[\]]/g);let s=t;for(const u of r){if(s===null||s===void 0)return n;const c=u.replace(/['"]/g,"");c.trim()!==""&&(s=s[c])}return s===void 0?n:s},F=(t,e,n)=>{if(!t)return{};if(!e||n===void 0)return t;const r=e.split(/[\.\[\]]/g).filter(c=>!!c.trim()),s=c=>{if(r.length>1){const o=r.shift(),a=/^\d+$/.test(r[0]);c[o]=c[o]===void 0?a?[]:{}:c[o],s(c[o])}else c[r[0]]=n},u=Z(t);return s(u),u},q=(t,e)=>!t||!e?t??e??{}:Object.entries({...t,...e}).reduce((n,[r,s])=>({...n,[r]:(()=>k(t[r])?q(t[r],s):s)()}),{}),I=t=>{if(!t)return[];const e=(n,r)=>k(n)?Object.entries(n).flatMap(([s,u])=>e(u,[...r,s])):w(n)?n.flatMap((s,u)=>e(s,[...r,`${u}`])):[r.join(".")];return e(t,[])},Kt=t=>t?M(I(t),e=>e,e=>D(t,e)):{},Wt=t=>t?Object.keys(t).reduce((e,n)=>F(e,n,t[n]),{}):{},_=(t,e)=>Math.floor(Math.random()*(e-t+1)+t),Jt=t=>{const e=t.length;if(e===0)return null;const n=_(0,e-1);return t[n]},Xt=t=>t.map(e=>({rand:Math.random(),value:e})).sort((e,n)=>e.rand-n.rand).map(e=>e.value),Yt=(t,e="")=>{const n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"+e;return B(t,r=>r+n.charAt(_(0,n.length-1)),"")},Ht=(t,e=n=>`${n}`)=>{const{indexesByKey:n,itemsByIndex:r}=t.reduce((f,l,g)=>({indexesByKey:{...f.indexesByKey,[e(l)]:g},itemsByIndex:{...f.itemsByIndex,[g]:l}}),{indexesByKey:{},itemsByIndex:{}}),s=(f,l)=>n[e(f)]n[e(f)]>n[e(l)]?f:l,c=()=>r[0],o=()=>r[t.length-1],a=(f,l)=>r[n[e(f)]+1]??l??c(),d=(f,l)=>r[n[e(f)]-1]??l??o();return{min:s,max:u,first:c,last:o,next:a,previous:d,spin:(f,l)=>{if(l===0)return f;const g=Math.abs(l),re=g>t.length?g%t.length:g;return $(0,re-1).reduce(v=>l>0?a(v):d(v),f)}}},p=t=>{if(!t||t.length===0)return"";const e=t.toLowerCase();return e.substring(0,1).toUpperCase()+e.substring(1,e.length)},Qt=t=>{const e=t?.replace(/([A-Z])+/g,p)?.split(/(?=[A-Z])|[\.\-\s_]/).map(n=>n.toLowerCase())??[];return e.length===0?"":e.length===1?e[0]:e.reduce((n,r)=>`${n}${r.charAt(0).toUpperCase()}${r.slice(1)}`)},Vt=(t,e)=>{const n=t?.replace(/([A-Z])+/g,p).split(/(?=[A-Z])|[\.\-\s_]/).map(s=>s.toLowerCase())??[];if(n.length===0)return"";if(n.length===1)return n[0];const r=n.reduce((s,u)=>`${s}_${u.toLowerCase()}`);return e?.splitOnNumber===!1?r:r.replace(/([A-Za-z]{1}[0-9]{1})/,s=>`${s[0]}_${s[1]}`)},Gt=t=>{const e=t?.replace(/([A-Z])+/g,p)?.split(/(?=[A-Z])|[\.\-\s_]/).map(n=>n.toLowerCase())??[];return e.length===0?"":e.length===1?e[0]:e.reduce((n,r)=>`${n}-${r.toLowerCase()}`)},xt=t=>{const e=t?.split(/[\.\-\s_]/).map(n=>n.toLowerCase())??[];return e.length===0?"":e.map(n=>n.charAt(0).toUpperCase()+n.slice(1)).join("")},te=t=>t?t.split(/(?=[A-Z])|[\.\-\s_]/).map(e=>e.trim()).filter(e=>!!e).map(e=>p(e.toLowerCase())).join(" "):"",ee=(t,e,n=/\{\{(.+?)\}\}/g)=>Array.from(t.matchAll(n)).reduce((r,s)=>r.replace(s[0],e[s[1]]),t),ne=(t,e=" ")=>{if(!t)return"";const n=e.replace(/[\W]{1}/g,"\\$&"),r=new RegExp(`^[${n}]+|[${n}]+$`,"g");return t.replace(r,"")};return i.all=bt,i.alphabetical=x,i.assign=q,i.boil=O,i.callable=jt,i.camel=Qt,i.capitalize=p,i.chain=At,i.clone=Z,i.cluster=it,i.compose=$t,i.construct=Wt,i.counting=tt,i.crush=Kt,i.dash=Gt,i.debounce=Tt,i.defer=yt,i.diff=gt,i.draw=Jt,i.first=V,i.flat=ct,i.fork=R,i.get=D,i.group=X,i.guard=Ot,i.inRange=zt,i.intersects=ot,i.invert=Dt,i.isArray=w,i.isDate=T,i.isEmpty=J,i.isEqual=j,i.isFloat=W,i.isFunction=y,i.isInt=K,i.isNumber=h,i.isObject=k,i.isPrimitive=N,i.isPromise=S,i.isString=U,i.isSymbol=E,i.iterate=B,i.keys=I,i.last=G,i.list=$,i.listify=It,i.lowerize=Ft,i.map=wt,i.mapEntries=Zt,i.mapKeys=P,i.mapValues=Lt,i.max=rt,i.memo=Nt,i.merge=ft,i.min=st,i.objectify=M,i.omit=Ut,i.parallel=pt,i.partial=Ct,i.partob=Pt,i.pascal=xt,i.pick=vt,i.proxied=_t,i.random=_,i.range=A,i.reduce=mt,i.replace=et,i.replaceOrAppend=lt,i.retry=kt,i.select=nt,i.series=Ht,i.set=F,i.shake=Bt,i.shift=ht,i.shuffle=Xt,i.sift=dt,i.sleep=C,i.snake=Vt,i.sort=z,i.sum=Q,i.template=ee,i.throttle=St,i.title=te,i.toFloat=Mt,i.toInt=Rt,i.toggle=at,i.trim=ne,i.try=m,i.tryit=m,i.uid=Yt,i.unique=ut,i.upperize=qt,i.zip=Y,i.zipToObject=H,i}({}); +var radash=function(s){"use strict";const _=t=>!!t&&t.constructor===Symbol,w=Array.isArray,k=t=>!!t&&t.constructor===Object,E=t=>t==null||typeof t!="object"&&typeof t!="function",y=t=>!!(t&&t.constructor&&t.call&&t.apply),U=t=>typeof t=="string"||t instanceof String,K=t=>h(t)&&t%1===0,W=t=>h(t)&&t%1!==0,h=t=>{try{return Number(t)===t}catch{return!1}},T=t=>Object.prototype.toString.call(t)==="[object Date]",S=t=>!(!t||!t.then||!y(t.then)),J=t=>{if(t===!0||t===!1||t==null)return!0;if(h(t))return t===0;if(T(t))return isNaN(t.getTime());if(y(t)||_(t))return!1;const e=t.length;if(h(e))return e===0;const n=t.size;return h(n)?n===0:Object.keys(t).length===0},j=(t,e)=>{if(Object.is(t,e))return!0;if(t instanceof Date&&e instanceof Date)return t.getTime()===e.getTime();if(t instanceof RegExp&&e instanceof RegExp)return t.toString()===e.toString();if(typeof t!="object"||t===null||typeof e!="object"||e===null)return!1;const n=Reflect.ownKeys(t),r=Reflect.ownKeys(e);if(n.length!==r.length)return!1;for(let u=0;ut.reduce((n,r)=>{const u=e(r);return n[u]||(n[u]=[]),n[u].push(r),n},{});function Y(...t){return!t||!t.length?[]:new Array(Math.max(...t.map(({length:e})=>e))).fill([]).map((e,n)=>t.map(r=>r[n]))}function H(t,e){if(!t||!t.length)return{};const n=y(e)?e:w(e)?(r,u)=>e[u]:(r,u)=>e;return t.reduce((r,u,i)=>(r[u]=n(u,i),r),{})}const O=(t,e)=>!t||(t.length??0)===0?null:t.reduce(e);function Q(t,e){return(t||[]).reduce((n,r)=>n+(e?e(r):r),0)}const V=(t,e=void 0)=>t?.length>0?t[0]:e,G=(t,e=void 0)=>t?.length>0?t[t.length-1]:e,z=(t,e,n=!1)=>{if(!t)return[];const r=(i,c)=>e(i)-e(c),u=(i,c)=>e(c)-e(i);return t.slice().sort(n===!0?u:r)},x=(t,e,n="asc")=>{if(!t)return[];const r=(i,c)=>`${e(i)}`.localeCompare(e(c)),u=(i,c)=>`${e(c)}`.localeCompare(e(i));return t.slice().sort(n==="desc"?u:r)},tt=(t,e)=>t?t.reduce((n,r)=>{const u=e(r);return n[u]=(n[u]??0)+1,n},{}):{},et=(t,e,n)=>{if(!t)return[];if(e===void 0)return[...t];for(let r=0;rr)=>t.reduce((r,u)=>(r[e(u)]=n(u),r),{}),nt=(t,e,n)=>t?t.reduce((r,u,i)=>(n(u,i)&&r.push(e(u,i)),r),[]):[];function rt(t,e){const n=e??(r=>r);return O(t,(r,u)=>n(r)>n(u)?r:u)}function ut(t,e){const n=e??(r=>r);return O(t,(r,u)=>n(r){const n=Math.ceil(t.length/e);return new Array(n).fill(null).map((r,u)=>t.slice(u*e,u*e+e))},it=(t,e)=>{const n=t.reduce((r,u)=>{const i=e?e(u):u;return r[i]||(r[i]=u),r},{});return Object.values(n)};function*A(t,e,n=u=>u,r=1){const u=y(n)?n:()=>n,i=e?t:0,c=e??t;for(let o=i;o<=c&&(yield u(o),!(o+r>c));o+=r);}const $=(t,e,n,r)=>Array.from(A(t,e,n,r)),ct=t=>t.reduce((e,n)=>(e.push(...n),e),[]),ot=(t,e,n)=>{if(!t||!e)return!1;const r=n??(i=>i),u=e.reduce((i,c)=>(i[r(c)]=!0,i),{});return t.some(i=>u[r(i)])},R=(t,e)=>t?t.reduce((n,r)=>{const[u,i]=n;return e(r)?[[...u,r],i]:[u,[...i,r]]},[[],[]]):[[],[]],ft=(t,e,n)=>!e&&!t?[]:e?t?n?t.reduce((r,u)=>{const i=e.find(c=>n(u)===n(c));return i?r.push(i):r.push(u),r},[]):t:[]:t,lt=(t,e,n)=>{if(!t&&!e)return[];if(!e)return[...t];if(!t)return[e];for(let r=0;r{if(!t&&!e)return[];if(!t)return[e];if(!e)return[...t];const u=n?(o,a)=>n(o,a)===n(e,a):o=>o===e;return t.find(u)?t.filter((o,a)=>!u(o,a)):(r?.strategy??"append")==="append"?[...t,e]:[e,...t]},dt=t=>t?.filter(e=>!!e)??[],B=(t,e,n)=>{let r=n;for(let u=1;u<=t;u++)r=e(r,u);return r},gt=(t,e,n=r=>r)=>{if(!t?.length&&!e?.length)return[];if(t?.length===void 0)return[...e];if(!e?.length)return[...t];const r=e.reduce((u,i)=>(u[n(i)]=!0,u),{});return t.filter(u=>!r[n(u)])};function ht(t,e){if(t.length===0)return t;const n=e%t.length;return n===0?t:[...t.slice(-n,t.length),...t.slice(0,-n)]}const mt=async(t,e,n)=>{const r=n!==void 0;if(!r&&t?.length<1)throw new Error("Cannot reduce empty array with no init value");const u=r?t:t.slice(1);let i=r?n:t[0];for(const[c,o]of u.entries())i=await e(i,o,c);return i},wt=async(t,e)=>{if(!t)return[];let n=[],r=0;for(const u of t){const i=await e(u,r++);n.push(i)}return n},yt=async t=>{const e=[],n=(i,c)=>e.push({fn:i,rethrow:c?.rethrow??!1}),[r,u]=await m(t)(n);for(const{fn:i,rethrow:c}of e){const[o]=await m(i)(r);if(o&&c)throw o}if(r)throw r;return u};class L extends Error{constructor(e=[]){super();const n=e.find(r=>r.name)?.name??"";this.name=`AggregateError(${n}...)`,this.message=`AggregateError with ${e.length} errors`,this.stack=e.find(r=>r.stack)?.stack??this.stack,this.errors=e}}const pt=async(t,e,n)=>{const r=e.map((d,b)=>({index:b,item:d})),u=async d=>{const b=[];for(;;){const f=r.pop();if(!f)return d(b);const[l,g]=await m(n)(f.item);b.push({error:l,result:g,index:f.index})}},i=$(1,t).map(()=>new Promise(u)),c=await Promise.all(i),[o,a]=R(z(c.flat(),d=>d.index),d=>!!d.error);if(o.length>0)throw new L(o.map(d=>d.error));return a.map(d=>d.result)};async function bt(t){const e=w(t)?t.map(u=>[null,u]):Object.entries(t),n=await Promise.all(e.map(([u,i])=>i.then(c=>({result:c,exc:null,key:u})).catch(c=>({result:null,exc:c,key:u})))),r=n.filter(u=>u.exc);if(r.length>0)throw new L(r.map(u=>u.exc));return w(t)?n.map(u=>u.result):n.reduce((u,i)=>({...u,[i.key]:i.result}),{})}const kt=async(t,e)=>{const n=t?.times??3,r=t?.delay,u=t?.backoff??null;for(const i of A(1,n)){const[c,o]=await m(e)(a=>{throw{_exited:a}});if(!c)return o;if(c._exited)throw c._exited;if(i===n)throw c;r&&await C(r),u&&await C(u(i))}},C=t=>new Promise(e=>setTimeout(e,t)),m=t=>(...e)=>{try{const n=t(...e);return S(n)?n.then(r=>[void 0,r]).catch(r=>[r,void 0]):[void 0,n]}catch(n){return[n,void 0]}},Ot=(t,e)=>{const n=u=>{if(e&&!e(u))throw u},r=u=>u instanceof Promise;try{const u=t();return r(u)?u.catch(n):u}catch(u){return n(u)}};function At(...t){return(...e)=>t.slice(1).reduce((n,r)=>r(n),t[0](...e))}function $t(...t){return t.reverse().reduce((e,n)=>n(e))}const Ct=(t,...e)=>(...n)=>t(...e,...n),Nt=(t,e)=>n=>t({...e,...n}),Pt=t=>new Proxy({},{get:(e,n)=>t(n)}),_t=(t,e,n,r)=>function(...i){const c=n?n(...i):JSON.stringify({args:i}),o=t[c];if(o!==void 0&&(!o.exp||o.exp>new Date().getTime()))return o.value;const a=e(...i);return t[c]={exp:r?new Date().getTime()+r:null,value:a},a},Et=(t,e={})=>_t({},t,e.key??null,e.ttl??null),Tt=({delay:t},e)=>{let n,r=!0;const u=(...i)=>{r?(clearTimeout(n),n=setTimeout(()=>{r&&e(...i),n=void 0},t)):e(...i)};return u.isPending=()=>n!==void 0,u.cancel=()=>{r=!1},u.flush=(...i)=>e(...i),u},St=({interval:t},e)=>{let n=!0,r;const u=(...i)=>{n&&(e(...i),n=!1,r=setTimeout(()=>{n=!0,r=void 0},t))};return u.isThrottled=()=>r!==void 0,u},jt=(t,e)=>{const n=()=>{};return new Proxy(Object.assign(n,t),{get:(r,u)=>r[u],set:(r,u,i)=>(r[u]=i,!0),apply:(r,u,i)=>e(Object.assign({},r))(...i)})};function zt(t,e,n){return typeof t=="number"&&typeof e=="number"&&(typeof n>"u"||typeof n=="number")?(typeof n>"u"&&(n=e,e=0),t>=Math.min(e,n)&&t{const n=e===void 0?0:e;if(t==null)return n;const r=parseFloat(t);return isNaN(r)?n:r},Rt=(t,e)=>{const n=e===void 0?0:e;if(t==null)return n;const r=parseInt(t);return isNaN(r)?n:r};function Bt(t,e=0){return Number(`${Math.round(Number(`${t}e+${e}`))}e-${e}`)}const Lt=(t,e=n=>n===void 0)=>t?Object.keys(t).reduce((r,u)=>(e(t[u])||(r[u]=t[u]),r),{}):{},N=(t,e)=>Object.keys(t).reduce((r,u)=>(r[e(u,t[u])]=t[u],r),{}),Zt=(t,e)=>Object.keys(t).reduce((r,u)=>(r[u]=e(t[u],u),r),{}),Dt=(t,e)=>t?Object.entries(t).reduce((n,[r,u])=>{const[i,c]=e(r,u);return n[i]=c,n},{}):{},Ft=t=>t?Object.keys(t).reduce((n,r)=>(n[t[r]]=r,n),{}):{},qt=t=>N(t,e=>e.toLowerCase()),It=t=>N(t,e=>e.toUpperCase()),Z=t=>{if(E(t))return t;if(typeof t=="function")return t.bind({});const e=new t.constructor;return Object.getOwnPropertyNames(t).forEach(n=>{e[n]=t[n]}),e},vt=(t,e)=>{if(!t)return[];const n=Object.entries(t);return n.length===0?[]:n.reduce((r,u)=>(r.push(e(u[0],u[1])),r),[])},Ut=(t,e)=>t?e.reduce((n,r)=>(Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]),n),{}):{},Kt=(t,e)=>t?!e||e.length===0?t:e.reduce((n,r)=>(delete n[r],n),{...t}):{},D=(t,e,n)=>{const r=e.split(/[\.\[\]]/g);let u=t;for(const i of r){if(u===null||u===void 0)return n;const c=i.replace(/['"]/g,"");c.trim()!==""&&(u=u[c])}return u===void 0?n:u},F=(t,e,n)=>{if(!t)return{};if(!e||n===void 0)return t;const r=e.split(/[\.\[\]]/g).filter(c=>!!c.trim()),u=c=>{if(r.length>1){const o=r.shift(),a=/^\d+$/.test(r[0]);c[o]=c[o]===void 0?a?[]:{}:c[o],u(c[o])}else c[r[0]]=n},i=Z(t);return u(i),i},q=(t,e)=>!t||!e?t??e??{}:Object.entries({...t,...e}).reduce((n,[r,u])=>({...n,[r]:(()=>k(t[r])?q(t[r],u):u)()}),{}),I=t=>{if(!t)return[];const e=(n,r)=>k(n)?Object.entries(n).flatMap(([u,i])=>e(i,[...r,u])):w(n)?n.flatMap((u,i)=>e(u,[...r,`${i}`])):[r.join(".")];return e(t,[])},Wt=t=>t?M(I(t),e=>e,e=>D(t,e)):{},Jt=t=>t?Object.keys(t).reduce((e,n)=>F(e,n,t[n]),{}):{},P=(t,e)=>Math.floor(Math.random()*(e-t+1)+t),Xt=t=>{const e=t.length;if(e===0)return null;const n=P(0,e-1);return t[n]},Yt=t=>t.map(e=>({rand:Math.random(),value:e})).sort((e,n)=>e.rand-n.rand).map(e=>e.value),Ht=(t,e="")=>{const n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"+e;return B(t,r=>r+n.charAt(P(0,n.length-1)),"")},Qt=(t,e=n=>`${n}`)=>{const{indexesByKey:n,itemsByIndex:r}=t.reduce((f,l,g)=>({indexesByKey:{...f.indexesByKey,[e(l)]:g},itemsByIndex:{...f.itemsByIndex,[g]:l}}),{indexesByKey:{},itemsByIndex:{}}),u=(f,l)=>n[e(f)]n[e(f)]>n[e(l)]?f:l,c=()=>r[0],o=()=>r[t.length-1],a=(f,l)=>r[n[e(f)]+1]??l??c(),d=(f,l)=>r[n[e(f)]-1]??l??o();return{min:u,max:i,first:c,last:o,next:a,previous:d,spin:(f,l)=>{if(l===0)return f;const g=Math.abs(l),ue=g>t.length?g%t.length:g;return $(0,ue-1).reduce(v=>l>0?a(v):d(v),f)}}},p=t=>{if(!t||t.length===0)return"";const e=t.toLowerCase();return e.substring(0,1).toUpperCase()+e.substring(1,e.length)},Vt=t=>{const e=t?.replace(/([A-Z])+/g,p)?.split(/(?=[A-Z])|[\.\-\s_]/).map(n=>n.toLowerCase())??[];return e.length===0?"":e.length===1?e[0]:e.reduce((n,r)=>`${n}${r.charAt(0).toUpperCase()}${r.slice(1)}`)},Gt=(t,e)=>{const n=t?.replace(/([A-Z])+/g,p).split(/(?=[A-Z])|[\.\-\s_]/).map(u=>u.toLowerCase())??[];if(n.length===0)return"";if(n.length===1)return n[0];const r=n.reduce((u,i)=>`${u}_${i.toLowerCase()}`);return e?.splitOnNumber===!1?r:r.replace(/([A-Za-z]{1}[0-9]{1})/,u=>`${u[0]}_${u[1]}`)},xt=t=>{const e=t?.replace(/([A-Z])+/g,p)?.split(/(?=[A-Z])|[\.\-\s_]/).map(n=>n.toLowerCase())??[];return e.length===0?"":e.length===1?e[0]:e.reduce((n,r)=>`${n}-${r.toLowerCase()}`)},te=t=>{const e=t?.split(/[\.\-\s_]/).map(n=>n.toLowerCase())??[];return e.length===0?"":e.map(n=>n.charAt(0).toUpperCase()+n.slice(1)).join("")},ee=t=>t?t.split(/(?=[A-Z])|[\.\-\s_]/).map(e=>e.trim()).filter(e=>!!e).map(e=>p(e.toLowerCase())).join(" "):"",ne=(t,e,n=/\{\{(.+?)\}\}/g)=>Array.from(t.matchAll(n)).reduce((r,u)=>r.replace(u[0],e[u[1]]),t),re=(t,e=" ")=>{if(!t)return"";const n=e.replace(/[\W]{1}/g,"\\$&"),r=new RegExp(`^[${n}]+|[${n}]+$`,"g");return t.replace(r,"")};return s.all=bt,s.alphabetical=x,s.assign=q,s.boil=O,s.callable=jt,s.camel=Vt,s.capitalize=p,s.chain=At,s.clone=Z,s.cluster=st,s.compose=$t,s.construct=Jt,s.counting=tt,s.crush=Wt,s.dash=xt,s.debounce=Tt,s.defer=yt,s.diff=gt,s.draw=Xt,s.first=V,s.flat=ct,s.fork=R,s.get=D,s.group=X,s.guard=Ot,s.inRange=zt,s.intersects=ot,s.invert=Ft,s.isArray=w,s.isDate=T,s.isEmpty=J,s.isEqual=j,s.isFloat=W,s.isFunction=y,s.isInt=K,s.isNumber=h,s.isObject=k,s.isPrimitive=E,s.isPromise=S,s.isString=U,s.isSymbol=_,s.iterate=B,s.keys=I,s.last=G,s.list=$,s.listify=vt,s.lowerize=qt,s.map=wt,s.mapEntries=Dt,s.mapKeys=N,s.mapValues=Zt,s.max=rt,s.memo=Et,s.merge=ft,s.min=ut,s.objectify=M,s.omit=Kt,s.parallel=pt,s.partial=Ct,s.partob=Nt,s.pascal=te,s.pick=Ut,s.proxied=Pt,s.random=P,s.range=A,s.reduce=mt,s.replace=et,s.replaceOrAppend=lt,s.retry=kt,s.round=Bt,s.select=nt,s.series=Qt,s.set=F,s.shake=Lt,s.shift=ht,s.shuffle=Yt,s.sift=dt,s.sleep=C,s.snake=Gt,s.sort=z,s.sum=Q,s.template=ne,s.throttle=St,s.title=ee,s.toFloat=Mt,s.toInt=Rt,s.toggle=at,s.trim=re,s.try=m,s.tryit=m,s.uid=Ht,s.unique=it,s.upperize=It,s.zip=Y,s.zipToObject=H,s}({}); diff --git a/src/index.ts b/src/index.ts index 9dff2e21..a301c4ed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -53,7 +53,7 @@ export { proxied, throttle } from './curry' -export { inRange, toFloat, toInt } from './number' +export { inRange, round, toFloat, toInt } from './number' export { assign, clone, diff --git a/src/number.ts b/src/number.ts index 8d7478cf..5b618709 100644 --- a/src/number.ts +++ b/src/number.ts @@ -65,3 +65,12 @@ export const toInt = ( const result = parseInt(value) return isNaN(result) ? def : result } + +/** + * Rounds `value` to `precision` decimal places, mirroring lodash's `round`. + * Uses exponential notation internally to avoid the common floating-point + * issue (e.g. `round(1.005, 2)` is `1.01`, not `1`). See issue #61. + */ +export function round(value: number, precision = 0): number { + return Number(`${Math.round(Number(`${value}e+${precision}`))}e-${precision}`) +} diff --git a/src/tests/number.test.ts b/src/tests/number.test.ts index ee83dde4..448253e1 100644 --- a/src/tests/number.test.ts +++ b/src/tests/number.test.ts @@ -93,3 +93,19 @@ describe('number module', () => { }) }) }) + +describe('round function', () => { + test('rounds to integer by default', () => { + assert.strictEqual(_.round(2.6), 3) + assert.strictEqual(_.round(2.4), 2) + }) + test('rounds to given precision avoiding floating point issues', () => { + assert.strictEqual(_.round(1.005, 2), 1.01) + assert.strictEqual(_.round(1.2345, 2), 1.23) + assert.strictEqual(_.round(1.235, 2), 1.24) + }) + test('handles zero value and negative numbers', () => { + assert.strictEqual(_.round(0, 2), 0) + assert.strictEqual(_.round(-1.5, 0), -1) + }) +})