Skip to content

Commit

Permalink
Bonnie v1.3 updates quantity null handling (#65)
Browse files Browse the repository at this point in the history
* treat null unit as '1', fixed cakefile to include failed test output, fix null for mismatch units

* build-everything

* whitespace fix

* fixes to quantity addition

* rebased and rebuilt

* un-exported an internal function, fixed a bug with coalesceToOne
  • Loading branch information
daco101 authored and jbradl11 committed Sep 26, 2018
1 parent c6b4cc2 commit c1d07d0
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 68 deletions.
4 changes: 2 additions & 2 deletions Cakefile
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ task "test", "run tests", ->
--recursive
--colors
", {maxBuffer: 2048 * 1024 }, (err, output) ->
throw err if err
console.log output
throw err if err

task "debug-test", "run tests", ->
invoke 'build'
Expand All @@ -89,5 +89,5 @@ task "debug-test", "run tests", ->
--debug-brk
./lib-test/
", { maxBuffer: 2048 * 1024 }, (err, output) ->
throw err if err
console.log output
throw err if err
53 changes: 25 additions & 28 deletions src/elm/quantity.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -96,25 +96,19 @@ module.exports.Quantity = class Quantity extends Expression

multiplyDivide: (other, operator) ->
if other instanceof Quantity
if @unit and other.unit
can_val = @to_ucum()
other_can_value = other.to_ucum()
ucum_value = ucum_multiply(can_val,[[operator,other_can_value]])
try
createQuantity(ucum_value.value, units_to_string(ucum_value.units))
catch
null
else
value = if operator == "/" then @value / other.value else @value * other.value
unit = @unit || other.unit
try
createQuantity(decimalAdjust("round",value,-8), unit)
catch
null
a = if this.unit? then this else new Quantity({value: this.value, unit: "1"})
b = if other.unit? then other else new Quantity({value: other.value, unit: "1"})
can_val = a.to_ucum()
other_can_value = b.to_ucum()
ucum_value = ucum_multiply(can_val,[[operator,other_can_value]])
try
createQuantity(ucum_value.value, units_to_string(ucum_value.units))
catch
null
else
value = if operator == "/" then @value / other else @value * other
try
createQuantity( decimalAdjust("round",value,-8), @unit)
createQuantity( decimalAdjust("round",value,-8), coalesceToOne(@unit) )
catch
null

Expand Down Expand Up @@ -248,29 +242,32 @@ module.exports.parseQuantity = (str) ->
else
null

module.exports.doAddition = (a,b) ->
doScaledAddition = (a,b,scaleForB) ->
if a instanceof Quantity and b instanceof Quantity
[a_unit, b_unit] = [coalesceToOne(a.unit), coalesceToOne(b.unit)]
# The units don't have to match (m and m^2), but must be convertable
# we will choose the unit of a to be the unit we return
val = convert_value(b.value, b.unit, a.unit)
val = convert_value(b.value * scaleForB, b_unit, a_unit)
return null unless val?
new Quantity({unit: a.unit, value: a.value + val})
new Quantity({unit: a_unit, value: a.value + val})
else if a.copy and a.add
b_unit = if b instanceof Quantity then coalesceToOne(b.unit) else b.unit
a.copy().add(b.value * scaleForB, clean_unit(b_unit))
else
a.copy?().add?(b.value, clean_unit(b.unit))
throw new Error("Unsupported argument types.")

module.exports.doSubtraction = (a,b) ->
if a instanceof Quantity and b instanceof Quantity
# The units don't have to match (m and m^2), but must be convertable
val = convert_value(b.value, b.unit, a.unit)
return null unless val?
new Quantity({unit: a.unit, value: a.value - val})
else
a.copy?().add?(b.value * -1 , clean_unit(b.unit))
module.exports.doAddition = (a,b) ->
doScaledAddition(a,b,1)

module.exports.doSubtraction = (a,b) ->
doScaledAddition(a,b,-1)

module.exports.doDivision = (a,b) ->
if a instanceof Quantity
a.dividedBy(b)

module.exports.doMultiplication = (a,b) ->
if a instanceof Quantity then a.multiplyBy(b) else b.multiplyBy(a)

coalesceToOne = (o) ->
if !o? or (o.trim? and !o.trim()) then '1' else o
78 changes: 40 additions & 38 deletions src/example/browser/cql4browsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6378,7 +6378,7 @@
},{"./builder":14,"./expression":20}],32:[function(require,module,exports){
// Generated by CoffeeScript 1.12.7
(function() {
var Code, Exception, Expression, FunctionRef, Quantity, ValueSet, build, clean_unit, convert_value, createQuantity, decimalAdjust, isValidDecimal, is_valid_ucum_unit, ref, ref1, ucum, ucum_multiply, ucum_time_units, ucum_to_cql_units, ucum_unit, unitValidityCache, units_to_string,
var Code, Exception, Expression, FunctionRef, Quantity, ValueSet, build, clean_unit, coalesceToOne, convert_value, createQuantity, decimalAdjust, doScaledAddition, isValidDecimal, is_valid_ucum_unit, ref, ref1, ucum, ucum_multiply, ucum_time_units, ucum_to_cql_units, ucum_unit, unitValidityCache, units_to_string,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;

Expand Down Expand Up @@ -6517,30 +6517,28 @@
};

Quantity.prototype.multiplyDivide = function(other, operator) {
var can_val, other_can_value, ucum_value, unit, value;
var a, b, can_val, other_can_value, ucum_value, value;
if (other instanceof Quantity) {
if (this.unit && other.unit) {
can_val = this.to_ucum();
other_can_value = other.to_ucum();
ucum_value = ucum_multiply(can_val, [[operator, other_can_value]]);
try {
return createQuantity(ucum_value.value, units_to_string(ucum_value.units));
} catch (error) {
return null;
}
} else {
value = operator === "/" ? this.value / other.value : this.value * other.value;
unit = this.unit || other.unit;
try {
return createQuantity(decimalAdjust("round", value, -8), unit);
} catch (error) {
return null;
}
a = this.unit != null ? this : new Quantity({
value: this.value,
unit: "1"
});
b = other.unit != null ? other : new Quantity({
value: other.value,
unit: "1"
});
can_val = a.to_ucum();
other_can_value = b.to_ucum();
ucum_value = ucum_multiply(can_val, [[operator, other_can_value]]);
try {
return createQuantity(ucum_value.value, units_to_string(ucum_value.units));
} catch (error) {
return null;
}
} else {
value = operator === "/" ? this.value / other : this.value * other;
try {
return createQuantity(decimalAdjust("round", value, -8), this.unit);
return createQuantity(decimalAdjust("round", value, -8), coalesceToOne(this.unit));
} catch (error) {
return null;
}
Expand Down Expand Up @@ -6746,36 +6744,32 @@
}
};

module.exports.doAddition = function(a, b) {
var base, val;
doScaledAddition = function(a, b, scaleForB) {
var a_unit, b_unit, ref2, val;
if (a instanceof Quantity && b instanceof Quantity) {
val = convert_value(b.value, b.unit, a.unit);
ref2 = [coalesceToOne(a.unit), coalesceToOne(b.unit)], a_unit = ref2[0], b_unit = ref2[1];
val = convert_value(b.value * scaleForB, b_unit, a_unit);
if (val == null) {
return null;
}
return new Quantity({
unit: a.unit,
unit: a_unit,
value: a.value + val
});
} else if (a.copy && a.add) {
b_unit = b instanceof Quantity ? coalesceToOne(b.unit) : b.unit;
return a.copy().add(b.value * scaleForB, clean_unit(b_unit));
} else {
return typeof a.copy === "function" ? typeof (base = a.copy()).add === "function" ? base.add(b.value, clean_unit(b.unit)) : void 0 : void 0;
throw new Error("Unsupported argument types.");
}
};

module.exports.doAddition = function(a, b) {
return doScaledAddition(a, b, 1);
};

module.exports.doSubtraction = function(a, b) {
var base, val;
if (a instanceof Quantity && b instanceof Quantity) {
val = convert_value(b.value, b.unit, a.unit);
if (val == null) {
return null;
}
return new Quantity({
unit: a.unit,
value: a.value - val
});
} else {
return typeof a.copy === "function" ? typeof (base = a.copy()).add === "function" ? base.add(b.value * -1, clean_unit(b.unit)) : void 0 : void 0;
}
return doScaledAddition(a, b, -1);
};

module.exports.doDivision = function(a, b) {
Expand All @@ -6792,6 +6786,14 @@
}
};

coalesceToOne = function(o) {
if ((o == null) || ((o.trim != null) && !o.trim())) {
return '1';
} else {
return o;
}
};

}).call(this);


Expand Down
44 changes: 44 additions & 0 deletions test/elm/quantity/test.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ describe 'Quantity', ->
minute.equals(new Quantity({unit: "minutes", value: 4})).should.be.true()
second.equals(new Quantity({unit: "seconds", value: 4})).should.be.true()
millisecond.equals(new Quantity({unit: "milliseconds", value: 4})).should.be.true()

it 'added to Quantity with invalid ucum units results in null', ->
quantity1 = new Quantity({unit:"m", value: 2})
quantity2 = new Quantity({unit: "m", value: 2})
Expand All @@ -103,3 +104,46 @@ describe 'Quantity', ->
quantity2.unit = "fakeUnit"
should(doDivision(quantity1, "/")).be.null()

it 'should convert units when possible to perform arithmetic', ->
divide = new Quantity({unit: "m", value: 8}).dividedBy(new Quantity({unit: "cm", value: 50}))
divide.equals(new Quantity({unit: "1", value: 16})).should.be.true()
multiply = new Quantity({unit: "cm", value: 8}).multiplyBy(new Quantity({unit: "m", value: 2}))
multiply.equals(new Quantity({unit: "m2", value: 0.16})).should.be.true()
add = doAddition(new Quantity({unit: "cm", value: 8}), new Quantity({unit: "m", value: 2}))
add.equals(new Quantity({unit: "m", value: 2.08})).should.be.true()
subtract = doSubtraction(new Quantity({unit: "cm", value: 150}), new Quantity({unit: "m", value: 1}))
subtract.equals(new Quantity({unit: "m", value: 0.5})).should.be.true()

it 'should return null when units are mismatched and cannot be converted', ->
add = doAddition(new Quantity({unit: "cm", value: 8}), new Quantity({unit: "g", value: 2}))
should.not.exist(add)
subtract = doSubtraction(new Quantity({unit: "cm", value: 150}), new Quantity({unit: "mg", value: 1}))
should.not.exist(subtract)

for unitName, unitValue of {Undefined: undefined, Null: null, EmptyString: ""}
it 'should treat unit:'+unitName+' the same as a unit:"1" in calculations', ->
divideWithOneOnRight = new Quantity({unit: "m", value: 8}).dividedBy(new Quantity({unit: "1", value: 2}))
divideWithNullOnRight = new Quantity({unit: "m", value: 8}).dividedBy(new Quantity({unit: unitValue, value: 2}))
divideWithOneOnRight.should.deepEqual(divideWithNullOnRight)
multiplyWithOneOnRight = new Quantity({unit: "m", value: 8}).multiplyBy(new Quantity({unit: "1", value: 2}))
multiplyWithNullOnRight = new Quantity({unit: "m", value: 8}).multiplyBy(new Quantity({unit: unitValue, value: 2}))
multiplyWithOneOnRight.should.deepEqual(multiplyWithNullOnRight)
addWithOneOnRight = doAddition(new Quantity({unit: "1", value: 8}), new Quantity({unit: "1", value: 2}))
addWithNullOnRight = doAddition(new Quantity({unit: "1", value: 8}), new Quantity({unit: unitValue, value: 2}))
addWithOneOnRight.should.deepEqual(addWithNullOnRight)
subtractWithOneOnRight = doSubtraction(new Quantity({unit: "1", value: 8}), new Quantity({unit: "1", value: 2}))
subtractWithNullOnRight = doSubtraction(new Quantity({unit: "1", value: 8}), new Quantity({unit: unitValue, value: 2}))
subtractWithOneOnRight.should.deepEqual(subtractWithNullOnRight)

divideWithOneOnLeft = new Quantity({unit: "1", value: 8}).dividedBy(new Quantity({unit: "m", value: 2}))
divideWithNullOnLeft = new Quantity({unit: unitValue, value: 8}).dividedBy(new Quantity({unit: "m", value: 2}))
divideWithOneOnLeft.should.deepEqual(divideWithNullOnLeft)
multiplyWithOneOnLeft = new Quantity({unit: "1", value: 8}).multiplyBy(new Quantity({unit: "m", value: 2}))
multiplyWithNullOnLeft = new Quantity({unit: unitValue, value: 8}).multiplyBy(new Quantity({unit: "m", value: 2}))
multiplyWithOneOnLeft.should.deepEqual(multiplyWithNullOnLeft)
addWithOneOnLeft = doAddition(new Quantity({unit: "1", value: 8}), new Quantity({unit: "1", value: 2}))
addWithNullOnLeft = doAddition(new Quantity({unit: unitValue, value: 8}), new Quantity({unit: "1", value: 2}))
addWithOneOnLeft.should.deepEqual(addWithNullOnLeft)
subtractWithOneOnLeft = doSubtraction(new Quantity({unit: "1", value: 8}), new Quantity({unit: "1", value: 2}))
subtractWithNullOnLeft = doSubtraction(new Quantity({unit: unitValue, value: 8}), new Quantity({unit: "1", value: 2}))
subtractWithOneOnLeft.should.deepEqual(subtractWithNullOnLeft)

0 comments on commit c1d07d0

Please sign in to comment.