Giter VIP home page Giter VIP logo

unitmath's People

Contributors

dependabot[bot] avatar ericman314 avatar harrysarson avatar m93a avatar nickewing avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

unitmath's Issues

simplify is overly aggressive for composed units

I have an issue with the simplify conversion of certain complex units, which seems overly aggressive to me.

The following is an example using two simple units: energy and energy by square meter (very common in many sectors).

// Simple energy
e = unit(123456, 'Wh')
console.log(e.simplify().toString()) // 123.456 kWh

// Energy by square meter
e = unit(123456, 'Wh / m^2')
console.log(e.simplify().toString()) // 444441600 kg / s^2

Clearly the second simplification does not make much sense, even if it is absolutely correct.
How can this be prevented?

Auto add quantities, base quantities, and/or unit systems

A few tests in math.js are still failing; these are related to creating custom units, and expecting those units to be used when simplifying expressions (they are not currently). In UnitMath, a unit has to be part of a system in order for it to appear in a simplified expression. When the chosen unit system is auto, N m becomes J, and ft lbf becomes BTU.

Say we do createUnit(mph, 1 mi/hr) in math.js. Currently, mi / hr does not simplify to mph as we would expect. To fix this, we need to assign mph as the VELOCITY unit of the us system. This is possible if you include additional options to UnitMath:

definitions: {
  units: {
    mph: '1 mi/hr'
  },
  unitSystems: {
    us: {
      VELOCITY: 'mph'
    }
  }
}

But math.js doesn't have access to the logic used to match quantities or systems, so it wouldn't be able to produce the unitSystems object above. Things get even more complicated if a new quantity is introduced. I imagine that many users of UnitMath (including math.js) will not want to be encumbered by the verbose definitions required to make custom units members of a unit system:

definitions: {
  units: {
    snap: '1 m/s^4'
  },
  unitSystems: {
    si: {
      JOUNCE: 'snap'
    }
  },
  quantities: {
    JOUNCE: 'LENGTH TIME^-4'
  }
}

We could, however, create a flag that would cause the unitSystems and quantities objects to be created automatically. That way, if we then do createUnit(mph, 1 mi/hr) followed by 5 mi / hr, we should get 5 mph. But 5 km / hr will still give 5 km/hr since it's a different unit system.

definitions: {
  units: {
    snap: {
      value: '1 m/s^4',
      system: 'si' // or 'auto' to auto-select based on value
    }
  }
}

So in math.js, we would merely add the system: 'auto' to each custom unit.

@harrysarson, @josdejong, any thoughts?

P.S. How it might work:

Just before the units are finished being created in UnitStore.js, we look at each unit and if it has the system key, we match its value to existing quantities. If an existing quantity matches, we use that, and if there is not a match, we add an additional quantity (snap_QUANT or something). Then, if system is an existing system we use that, and if it is 'auto' then we match value to a unit system (si in this case due to the m). Then we add the additional entries to quantities and unitSystems. Since system is part of the unit definition, the user can retrieve the current definitions using unit.definitions(), recovering the original options passed.

Bug: Error on expo app with metro on import

Seems to not be an issue in vitest but on metro, the bundler error.
getting this error:

Unable to resolve "./src/Unit.js" from "../../node_modules/unitmath/index.js"

tried 1.1.0 and 1.0.0 version.

importing import unit from "unitmath/es/UnitMath" seems to work for now

compare() doesn't handle NaN very well

The compare method isn't consistent in its handling of NaNs. It should sort NaN values consistently. At the moment it breaks the sorting as calls to the compare function return surprise values.

Support for adding values with different units

There are a number of scenarios where you would like to aggregate units of different types together in a "sum".

A simple example might be ingredients for a recipe. Native support for a unit sum makes it easy to do things like combine the ingredients for any number of recipes together with simple addition.

Beyond addition, there are other operations that make sense in the context of a UnitSum. For example there are two definitions of "inner product" that are valuable.

One is defined as the sum-product of "exact" unit matches, so (2 egg + 2 tomato + 2 egg / day) • (3 egg) == 6 egg

The other is the sum-product of "fuzzy" unit matches,
so (1 egg / day + 1 croissant / mile) • (2 day + 4 mile) = (2 egg + 4 croissant)

All of the above is implemented in https://observablehq.com/@ajbouh/math-js-with-unitsum

I'd love to contribute this upstream!

I should add that there are a few open questions, like what the names for these operations should be and how to avoid needing to override so many internal mathjs operations.

Transform between systems (US, Imperial, Metric, etc)

Referencing josdejong/mathjs#1449 (comment)

I haven't found anything that solves the US system, Imperial, Metric differences of cup, Tbs etc well, same issue happens in measuring-cup. The need of

  1. Transform between systems (US, Imperial, Metric, etc)
  2. Transform between units (cup of sugar -> g of sugar)

Are very common use cases for cooking, (the 2nd point is a total different issue, but would be amazing to have something to do it).

For the second point maybe a complement like Moment timezone, could store all the transformations density of common ingredient, and some way to add explicit densities would be great.

Avoiding the precision loss footgun

UnitMath is shaping up nicely :) I have a question:

From the README (v0.3)

This means that if your custom type cannot be represented exactly by a floating-point number, you must use the two-argument unit(value, unitString) to construct units:

// Correct
let d = unit(Decimal('3.1415926535897932384626433832795', 'rad'))
let f = unit(Fraction(1, 2), 'kg') 
// Incorrect
let d = unit('3.1415926535897932384626433832795 rad') // Will lose precision
let f = unit('1 / 2 kg') // Parse error

This seems like an unnecessary footgun to introduce and I wonder if it is not possible to provide a better alternative.

Add a `getMatchingUnits` method

Querying quantities: I do not think it will be possible to "filter on all related units" using the quantity value. Defining this value tells the API to create a new base unit, like length or time. So when defining a unit such as mph, there will not be a quantity field. Instead of quantity: 'SPEED', it will have the property dimension: { LENGTH: 1, TIME: -1 }, which it determines from the definition: "mph": "1 mi / hr".

Having a quantity property for each unit was very appealing but I realized it was probably unnecessary. This is because there is already an equalsQuantity method that compares the quantity of one unit with another, by comparing their dimension objects. In theory you could create your own reference object of units to compare against:

const refQuantities = {
  MASS: 'kg',
  SPEED: 'm/s',
  ...
];

let myUnit = unit('4 mile/hour');
myUnit.equalsQuantity(refQuantities.SPEED); // true

I realize it's not quite as convenient as writing myUnit.quantity, but I really want to slim down the library to only the essential parts. I wanted to make it super easy to define your own units without requiring tons of boilerplate definitions. Perhaps eventually we could add a built-in feature to spit out "SPEED" automatically, but I don't want the inner workings of the library to be dependent on this feature, as it would require users to specify these quantities for each custom unit they add. (We could also add a getMatchingUnits method which returns an array of all the defined units which match the given unit. This could be used to populate a drop-down selector for a unit conversion tool, for example.)

Originally posted by @ericman314 in #34 (comment)

Exactly as stated in the above reply, a new getMatchingUnits method would be of great use to developers. It would make developing front end unit manipulation components a lot easier.

With the new method, a personal use case where it would be beneficial involves simplifying the design of a data grid. Developers could populate a column with units of a specific quantity. The system would then allow the end user to select the display units from a custom component located in the column header. This component would include a dropdown menu with valid matching units for the given quantity.

It would also be useful for the library to provide a default reference quantities refQuantities object, populated with the most common physical quantities, such as the mentioned "MASS", "SPEED", but also uncompounded powers of base quantities such as "AREA" and "VOLUME". Developers could of course define their own object (or update the default one through the config function/method of the module) for any custom quantities.

Such an addition would also make it easier to more easily enforce certain quantities when end users input units through making it possible to validate the input against these reference quantities.

Feature request: function to select the "best" unit among a list

Sometimes you want to convert a given unit to either X, Y or Z, and you need to select the best.
It would be very handy in cases where you need for example to design an UI, and the UI (due for example to client requests) can only have units m and cm, but not for example dm, in or dam.

It is quite simple to convert the unit to all of them, then simply select the first one with value above (or closest to?) your minimum value (prefixMin in unitmath lingo).

This could be a nice little handy feature to be added to this nice little handy library!

Alternate method for defining built-in or custom units

It would be terrific if custom and built-in units could be defined based off of other units.

Example:

const UNITS = {
  // length
  meter: {
    base: DIMENSIONS.LENGTH,
    prefixes: PREFIXES.LONG,
    commonPrefixes: ['nano', 'micro', 'milli', 'centi', '', 'kilo'],
    value: 1
  },
  m: {
    prefixes: PREFIXES.SHORT,
    commonPrefixes: ['n', 'u', 'm', 'c', '', 'k'],
    value: '1 meter'
  },
  inch: {
    value: '0.0254 m'
  },
  foot: '12 inch',
  ...

Advantages:

  • Simpler way to define new units
  • Retains backwards compatibility
  • Default properties could be omitted (i.e., PREFIXES.NONE)
  • Shorthand syntax (see foot versus inch above)
  • Would reduce UnitStore.js file size
  • Defining custom units and built-in units uses the exact same code
  • Less prone to errors while defining units
  • Wouldn't have to declare the unit's DIMENSIONS or determine the unit's value in SI units

Disadvantages:

  • Would require a good deal of refactoring
  • Longer processing time (only once per configuration, and config changes where only formatting options change wouldn't require re-processing)
  • Would either have to 1) require that units are declared "in order", or 2) use multiple passes to process the units.
  • Redefinitions of units such as kg would cause dependent units to change as well, which might be considered good or bad depending on the circumstances.

Bundling

What files do you plan to include in the published version? I have thought of the following:

  1. An module entry point - pointed to by the "module" field in package.json.
  2. A node entry point - pointed to by the "main" field in package.json.
  3. A bundled es module for use in very new browsers.
  4. A bundled UMD file for use in browsers that are no IE 11.
  5. A bundled UMD that has been run through babel for IE 11.

Implement an explicit method to prevent a unit from being simplified

From the docs:

If the prefix or simplify options are set to 'auto' or 'always', the toString and format methods will try to simplify the unit before outputting. This can be prevented by calling .to() on a unit with no parameters, which will return a new unit that will not be simplified automatically.

It feels a bit like a side effect to use .to() to prevent a unit from being simplified. Maybe that could use an explicit function, like .fixUnits() or .disableSimplify() or unit('2 m/s', { simplify: false}) or so. What do you think?

valueOf should be useful for sorting

Lodash forces objects into values via valueOf when sorting. This would be useful for sorting arrays of units, without having to pass custom compare function, etc.

v1.0

v1.0 is getting very close! Thank you to @m93a for adding typescript support, and everyone else for your helpful comments.

Just a few more things left I can think of:

  • Edit README.md for accuracy, style, conciseness, and completeness
  • Update HISTORY.md
  • Figure out how to emit type declarations correctly
  • Try out the api in a project and make sure it's not weird to use
  • Write a short migration guide, or just a section on the breaking changes

Parsing Rules

Currently in Math.js, the parser in Unit.js parses input such that the last two units below are parsed as 8.314 (J / mol) * K, and are not equal to the first two:

8.314 J / mol / K
8.314 J / (mol K)
8.314 J / mol K
8.314 J / mol * K

I would like to propose a much simpler set of parsing rules that would make all these units equal:

  • All parentheses and *'s are ignored.
  • All units appearing before the first / are in the numerator.
  • All units after the first / are in the denominator.
  • Subsequent /'s are ignored.

Being able to parse multiple *, /, and parentheses is cool, but it really seems beyond the scope of this library.

If I am not mistaken, in Math.js, math.eval constructs compound units by parsing each individual node, then uses its own comprehensive parsing rules to build a tree. Only if a user calls math.Unit() directly would the Unit.js parser be invoked. So this change probably would not affect typical Math.js usage very much, I think.

SyntaxError: Cannot use import statement outside a module

Hello, maybe this is a very dumb question, but I can't figure out how to test this package.

If I manually load it in Node using require, after installing with npm i unitmath:

> require('unitmath')
/dev/shm/unitmath/node_modules/unitmath/index.js:1
import UnitMath from './src/Unit.js'
^^^^^^

Uncaught:
SyntaxError: Cannot use import statement inside the Node.js REPL, alternatively use dynamic import:
>

Node using import:

> await import ("unitmath")
(node:145890) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
Uncaught [SyntaxError: Unexpected token (1:13)
] {
  pos: 13,
  loc: Position { line: 1, column: 13 },
  raisedAt: 23
}

Deno using import:

> await import('unitmath')
Uncaught TypeError: 'import', and 'export' cannot be used outside of module code at file:///dev/shm/unitmath/node_modules/.deno/[email protected]/node_modules/unitmath/index.js:1:1

  import UnitMath from './src/Unit.js'
  ~~~~~~
    at async <anonymous>:1:22

The same seem to occur when running a .js or .mjs file with the following content:

const unit = require('unitmath')

let a = unit('5 m').div('2 s')   // 2.5 m / s
let b = unit('40 km').to('mile')  // 24.8548476894934 mile
b.toString({ precision: 4 })          // "24.85 mile"

What am I doing wrong?

Make all units immutable

Related to #1 - an idea I would like to throw out:

The API will be cleaner, bugs harder to introduce and maintenance a lot less of a burden if every type defined in unitmath is immutable*.

The major downside would be a possible performance cost but I would argue against any premature optimisations without benchmarks that show cloning units is a bottleneck.


Related: when configuring the global options, any changes made in one file should be independent from changes made in other files. Currently

unit.config(options)

updates the global unit configuration. A better API might be

newUnit = oldUnit.config(options)

where the config() function returns a brand new instance.


*: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#Browser_compatibility

API is getting too complicated and it's not even at v1.0 yet

I think part of the reason development has stalled, is the API is getting too complicated. The "auto add to unit system" feature, which is needed by mathjs, is what pushed me over the edge. As an experiment, I am starting a new branch where I am stripping out base quantities, quantities, and unit systems, and keeping only the units and prefixes--and see how many of the features I can retain with these simplifications. @josdejong, someday I hope it can be integrated into math.js.

Proposed API

I've added a proposed API to the README. Please share your comments and ideas below!

`for(let unit in ...) { unit.quantity`

Hey Eric!
I've been reading through the code of make-things-less-complicated and there's a part that didn't make sense to me. On line 384 of Unit.ts there is this code:

// Did not find a matching unit in the system
// 4. Build a representation from the base units of all defined units
for (let dim in result.dimension) {
  if (Math.abs(result.dimension[dim] || 0) > 1e-12) {
    let found = false
    for (let unit in unitStore.defs.units) {
      if (unit.quantity === dim) {
        // TODO: Try to use a matching unit from the specified system, instead of the base unit that was just found
        proposedUnitList.push({
          unit,
          prefix: unit.basePrefix || '',
          power: result.dimension[dim]
        })
        found = true
        break
      }
    }
    if (!found) ok = false
  }
}

The for..in loops define variables dim and unit as the keys of an object, right? Then how could unit.quantity and unit.basePrefix be anything other than undefined? Did you mean something like this instead?

for (let unit of Object.values(unitStore.defs.units)) {

The Object.values function returns the values of enumerable properties of an object.

Dependency resolving issue

When I try to compile dependabot update PR for Unitmath v1.0.0 I see the following:

ERROR in ../../node_modules/unitmath/index.js 1:0-36
--
  | Module not found: Error: Can't resolve './src/Unit.js' in '/home/app/current/node_modules/unitmath'
  | resolve './src/Unit.js' in '/home/app/current/node_modules/unitmath'
  | using description file: /home/app/current/node_modules/unitmath/package.json (relative path: .)
  | Field 'browser' doesn't contain a valid alias configuration
  | using description file: /home/app/current/node_modules/unitmath/package.json (relative path: ./src/Unit.js)
  | no extension
  | Field 'browser' doesn't contain a valid alias configuration
  | /home/app/current/node_modules/unitmath/src/Unit.js doesn't exist
  | .ts
  | Field 'browser' doesn't contain a valid alias configuration
  | /home/app/current/node_modules/unitmath/src/Unit.js.ts doesn't exist
  | .tsx
  | Field 'browser' doesn't contain a valid alias configuration
  | /home/app/current/node_modules/unitmath/src/Unit.js.tsx doesn't exist
  | .js
  | Field 'browser' doesn't contain a valid alias configuration
  | /home/app/current/node_modules/unitmath/src/Unit.js.js doesn't exist
  | .jsx
  | Field 'browser' doesn't contain a valid alias configuration
  | /home/app/current/node_modules/unitmath/src/Unit.js.jsx doesn't exist
  | .min.js
  | Field 'browser' doesn't contain a valid alias configuration
  | /home/app/current/node_modules/unitmath/src/Unit.js.min.js doesn't exist
  | as directory
  | /home/app/current/node_modules/unitmath/src/Unit.js doesn't exist

I think it's taking issue with import UnitMath from "./src/Unit.js"; in the index.js as inside /src there is only Unit.ts - should this be changed to Unit.ts for it to work?

Or perhaps something not quite right with rollup config?

How to prevent users from accidentally forgetting to implement a custom type function?

The library looks really great already 👍

After reading #15, I was thinking: there are about 10 functions that you have to implement to support a custom type like bignumbers. If I'm not mistaken, the library will not warn you when you have forgotten to implement some of them. This may give unexpected behavior that could cause loss of precision for example.

How about either forcing to implement all of the functions, or throwing a warning when using a custom type function that is not overridden whilst others are?

Deployment workflow

This is a reminder for me to put together a workflow for deploying new versions.

What happens when adding units with different numerical types?

We have proposed allowing unitmath to be extended with custom numeric types by defining add, multiply etc in the config.

What happens if a user defines a unit based on a custom numeric type and tries to add a unit based on Numbers to it? What happens when a user tries to add two units based on different custom numeric types?

Typescript Support for users of the library

I'm using this awesome library in a larger TS project and it would be awesome to somehow have types for the objects returned by the library. I'm on an older version right now but the version 1.0.0 seems to not export any type information.
I saw that you started to adopt TS for the development of the library. Are there plans to somehow export types for users of this library? I'm no sure if i understand the code enough, but would you be interested in a Pull Request?

TypeScript?

Do you consider providing TypeScript definitions for the API? Or, better, porting the source code to TypeScript entirely? I consider using this package in a repository where type safety is crucial, so this would be a welcome addition for me.

If you do consider any of these two things, I'll be hapy to help with that in any way I can :)

Simplify the API of the formatter

Looking at the docs of the custom formatter, https://github.com/ericman314/UnitMath#custom-formatter , I have the feeling that the formatter option can be simpler, by removing the possibility to pass additional arguments to it via toString. I think you can achieve that already by changing:

unit('3.14159 rad').toString({ formatter: funnyFormatter }, '$', '_'

to

unit('3.14159 rad').toString({ formatter: value => funnyFormatter(value, '$', '_') }

// or 

const funnyFormatter$ = value => funnyFormatter(value, '$', '_')
unit('3.14159 rad').toString({ formatter: funnyFormatter$ }

API for custom units

I would like to somehow make the config a place for custom units. I do not think it is necessary to have the createUnit function to add units one at a time. Instead I think it best if all the custom units are included in the config and declared all at once. In fact, I would like to expose a frozen object of all the currently defined units somewhere, so that a user may query all the units and better discover how they work and are declared, and then call config with their own units, supplying an object with the same structure, which will extend the built-in units and return a new config.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.