Skip to content

Commit 060c498

Browse files
committed
Fix mathematical operations with different units
1 parent bc1dda5 commit 060c498

File tree

4 files changed

+197
-80
lines changed

4 files changed

+197
-80
lines changed

ast.cpp

Lines changed: 184 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,19 @@ namespace Sass {
597597
denominator_units_(vector<string>()),
598598
hash_(0)
599599
{
600-
if (!u.empty()) numerator_units_.push_back(u);
600+
size_t l = 0, r = 0;
601+
if (!u.empty()) {
602+
bool nominator = true;
603+
while (true) {
604+
r = u.find_first_of("*/", l);
605+
string unit(u.substr(l, r - l));
606+
if (nominator) numerator_units_.push_back(unit);
607+
else denominator_units_.push_back(unit);
608+
if (u[r] == '/') nominator = false;
609+
if (r == string::npos) break;
610+
else l = r + 1;
611+
}
612+
}
601613
concrete_type(NUMBER);
602614
}
603615

@@ -619,67 +631,169 @@ namespace Sass {
619631
bool Number::is_unitless()
620632
{ return numerator_units_.empty() && denominator_units_.empty(); }
621633

622-
void Number::normalize(string to)
634+
void Number::normalize(const string& prefered)
623635
{
624-
// (multiple passes because I'm too tired to think up something clever)
625-
// Find a unit to convert everything to, if one isn't provided.
626-
if (to.empty()) {
627-
for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) {
628-
string u(numerator_units_[i]);
629-
if (string_to_unit(u) == INCOMMENSURABLE) {
630-
continue;
631-
}
632-
else {
633-
to = u;
634-
break;
635-
}
636+
637+
// first make sure same units cancel each other out
638+
// it seems that a map table will fit nicely to do this
639+
// we basically construct exponents for each unit
640+
// has the advantage that they will be pre-sorted
641+
map<string, int> exponents;
642+
643+
// initialize by summing up occurences in unit vectors
644+
for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) ++ exponents[numerator_units_[i]];
645+
for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) -- exponents[denominator_units_[i]];
646+
647+
// the final conversion factor
648+
double factor = 1;
649+
650+
// get the first entry of numerators
651+
// forward it when entry is converted
652+
vector<string>::iterator nom_it = numerator_units_.begin();
653+
vector<string>::iterator nom_end = numerator_units_.end();
654+
vector<string>::iterator denom_it = denominator_units_.begin();
655+
vector<string>::iterator denom_end = denominator_units_.end();
656+
657+
// main normalization loop
658+
// should be close to optimal
659+
while (denom_it != denom_end)
660+
{
661+
// get and increment afterwards
662+
const string denom = *(denom_it ++);
663+
// skip already canceled out unit
664+
if (exponents[denom] >= 0) continue;
665+
// skip all units we don't know how to convert
666+
if (string_to_unit(denom) == INCOMMENSURABLE) continue;
667+
// now search for nominator
668+
while (nom_it != nom_end)
669+
{
670+
// get and increment afterwards
671+
const string nom = *(nom_it ++);
672+
// skip already canceled out unit
673+
if (exponents[nom] <= 0) continue;
674+
// skip all units we don't know how to convert
675+
if (string_to_unit(nom) == INCOMMENSURABLE) continue;
676+
// we now have two convertable units
677+
// add factor for current conversion
678+
factor *= conversion_factor(nom, denom);
679+
// update nominator/denominator exponent
680+
-- exponents[nom]; ++ exponents[denom];
681+
// inner loop done
682+
break;
636683
}
637684
}
638-
if (to.empty()) {
639-
for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) {
640-
string u(denominator_units_[i]);
641-
if (string_to_unit(u) == INCOMMENSURABLE) {
642-
continue;
643-
}
644-
else {
645-
to = u;
646-
break;
647-
}
685+
686+
// now we can build up the new unit arrays
687+
numerator_units_.clear();
688+
denominator_units_.clear();
689+
690+
// build them by iterating over the exponents
691+
for (auto exp : exponents)
692+
{
693+
// maybe there is more effecient way to push
694+
// the same item multiple times to a vector?
695+
for(size_t i = 0, S = abs(exp.second); i < S; ++i)
696+
{
697+
// opted to have these switches in the inner loop
698+
// makes it more readable and should not cost much
699+
if (exp.second < 0) denominator_units_.push_back(exp.first);
700+
else if (exp.second > 0) numerator_units_.push_back(exp.first);
648701
}
649702
}
650-
// Now loop through again and do all the conversions.
651-
for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) {
652-
string from(numerator_units_[i]);
653-
if (string_to_unit(from) == INCOMMENSURABLE) continue;
654-
value_ *= conversion_factor(from, to);
655-
numerator_units_[i] = to;
656-
}
657-
for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) {
658-
string from(denominator_units_[i]);
659-
if (string_to_unit(from) == INCOMMENSURABLE) continue;
660-
value_ /= conversion_factor(from, to);
661-
denominator_units_[i] = to;
662-
}
663-
// Now divide out identical units in the numerator and denominator.
664-
vector<string> ncopy;
665-
ncopy.reserve(numerator_units_.size());
666-
for (vector<string>::iterator n = numerator_units_.begin();
667-
n != numerator_units_.end();
668-
++n) {
669-
vector<string>::iterator d = find(denominator_units_.begin(),
670-
denominator_units_.end(),
671-
*n);
672-
if (d != denominator_units_.end()) {
673-
denominator_units_.erase(d);
674-
}
675-
else {
676-
ncopy.push_back(*n);
703+
704+
// apply factor to value_
705+
// best precision this way
706+
value_ *= factor;
707+
708+
// maybe convert to other unit
709+
// easier implemented on its own
710+
convert(prefered);
711+
712+
}
713+
714+
void Number::convert(const string& prefered)
715+
{
716+
// abort if unit is empty
717+
if (prefered.empty()) return;
718+
719+
// first make sure same units cancel each other out
720+
// it seems that a map table will fit nicely to do this
721+
// we basically construct exponents for each unit
722+
// has the advantage that they will be pre-sorted
723+
map<string, int> exponents;
724+
725+
// initialize by summing up occurences in unit vectors
726+
for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) ++ exponents[numerator_units_[i]];
727+
for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) -- exponents[denominator_units_[i]];
728+
729+
// the final conversion factor
730+
double factor = 1;
731+
732+
vector<string>::iterator denom_it = denominator_units_.begin();
733+
vector<string>::iterator denom_end = denominator_units_.end();
734+
735+
// main normalization loop
736+
// should be close to optimal
737+
while (denom_it != denom_end)
738+
{
739+
// get and increment afterwards
740+
const string denom = *(denom_it ++);
741+
// check if conversion is needed
742+
if (denom == prefered) continue;
743+
// skip already canceled out unit
744+
if (exponents[denom] >= 0) continue;
745+
// skip all units we don't know how to convert
746+
if (string_to_unit(denom) == INCOMMENSURABLE) continue;
747+
// we now have two convertable units
748+
// add factor for current conversion
749+
factor *= conversion_factor(denom, prefered);
750+
// update nominator/denominator exponent
751+
++ exponents[denom]; -- exponents[prefered];
752+
}
753+
754+
vector<string>::iterator nom_it = numerator_units_.begin();
755+
vector<string>::iterator nom_end = numerator_units_.end();
756+
757+
// now search for nominator
758+
while (nom_it != nom_end)
759+
{
760+
// get and increment afterwards
761+
const string nom = *(nom_it ++);
762+
// check if conversion is needed
763+
if (nom == prefered) continue;
764+
// skip already canceled out unit
765+
if (exponents[nom] <= 0) continue;
766+
// skip all units we don't know how to convert
767+
if (string_to_unit(nom) == INCOMMENSURABLE) continue;
768+
// we now have two convertable units
769+
// add factor for current conversion
770+
factor *= conversion_factor(nom, prefered);
771+
// update nominator/denominator exponent
772+
-- exponents[nom]; ++ exponents[prefered];
773+
}
774+
775+
// now we can build up the new unit arrays
776+
numerator_units_.clear();
777+
denominator_units_.clear();
778+
779+
// build them by iterating over the exponents
780+
for (auto exp : exponents)
781+
{
782+
// maybe there is more effecient way to push
783+
// the same item multiple times to a vector?
784+
for(size_t i = 0, S = abs(exp.second); i < S; ++i)
785+
{
786+
// opted to have these switches in the inner loop
787+
// makes it more readable and should not cost much
788+
if (exp.second < 0) denominator_units_.push_back(exp.first);
789+
else if (exp.second > 0) numerator_units_.push_back(exp.first);
677790
}
678791
}
679-
numerator_units_ = ncopy;
680-
// Sort the units to make them pretty and, well, normal.
681-
sort(numerator_units_.begin(), numerator_units_.end());
682-
sort(denominator_units_.begin(), denominator_units_.end());
792+
793+
// apply factor to value_
794+
// best precision this way
795+
value_ *= factor;
796+
683797
}
684798

685799
// useful for making one number compatible with another
@@ -697,6 +811,21 @@ namespace Sass {
697811
}
698812

699813

814+
bool Number::operator== (Expression* rhs) const
815+
{
816+
Number l(pstate_, value_, unit());
817+
Number& r = dynamic_cast<Number&>(*rhs);
818+
l.normalize(find_convertible_unit());
819+
r.normalize(find_convertible_unit());
820+
return l.unit() == r.unit() &&
821+
l.value() == r.value();
822+
}
823+
824+
bool Number::operator== (Expression& rhs) const
825+
{
826+
return operator==(&rhs);
827+
}
828+
700829
Expression* Hashed::at(Expression* k) const
701830
{
702831
if (elements_.count(k))

ast.hpp

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,25 +1170,13 @@ namespace Sass {
11701170
string unit() const;
11711171

11721172
bool is_unitless();
1173-
void normalize(string to = "");
1173+
void convert(const string& unit = "");
1174+
void normalize(const string& unit = "");
11741175
// useful for making one number compatible with another
11751176
string find_convertible_unit() const;
11761177

1177-
virtual bool operator==(Expression& rhs) const
1178-
{
1179-
try
1180-
{
1181-
Number& e(dynamic_cast<Number&>(rhs));
1182-
if (!e) return false;
1183-
e.normalize(find_convertible_unit());
1184-
return unit() == e.unit() && value() == e.value();
1185-
}
1186-
catch (std::bad_cast&)
1187-
{
1188-
return false;
1189-
}
1190-
catch (...) { throw; }
1191-
}
1178+
virtual bool operator== (Expression& rhs) const;
1179+
virtual bool operator== (Expression* rhs) const;
11921180

11931181
virtual size_t hash()
11941182
{

eval.cpp

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,7 @@ namespace Sass {
789789

790790
Expression* Eval::operator()(Number* n)
791791
{
792+
n->normalize();
792793
// behave according to as ruby sass (add leading zero)
793794
return new (ctx.mem) Number(n->pstate(),
794795
n->value(),
@@ -1055,13 +1056,8 @@ namespace Sass {
10551056
} break;
10561057

10571058
case Expression::NUMBER: {
1058-
Number* l = static_cast<Number*>(lhs);
1059-
Number* r = static_cast<Number*>(rhs);
1060-
Number tmp_r(*r);
1061-
tmp_r.normalize(l->find_convertible_unit());
1062-
return l->unit() == tmp_r.unit() && l->value() == tmp_r.value()
1063-
? true
1064-
: false;
1059+
return *static_cast<Number*>(lhs) ==
1060+
*static_cast<Number*>(rhs);
10651061
} break;
10661062

10671063
case Expression::COLOR: {
@@ -1152,8 +1148,8 @@ namespace Sass {
11521148
v->denominator_units() = r->denominator_units();
11531149
}
11541150

1155-
v->value(ops[op](lv, tmp.value()));
11561151
if (op == Binary_Expression::MUL) {
1152+
v->value(ops[op](lv, rv));
11571153
for (size_t i = 0, S = r->numerator_units().size(); i < S; ++i) {
11581154
v->numerator_units().push_back(r->numerator_units()[i]);
11591155
}
@@ -1162,14 +1158,17 @@ namespace Sass {
11621158
}
11631159
}
11641160
else if (op == Binary_Expression::DIV) {
1161+
v->value(ops[op](lv, rv));
11651162
for (size_t i = 0, S = r->numerator_units().size(); i < S; ++i) {
11661163
v->denominator_units().push_back(r->numerator_units()[i]);
11671164
}
11681165
for (size_t i = 0, S = r->denominator_units().size(); i < S; ++i) {
11691166
v->numerator_units().push_back(r->denominator_units()[i]);
11701167
}
1168+
} else {
1169+
v->value(ops[op](lv, tmp.value()));
11711170
}
1172-
v->normalize();
1171+
// v->normalize();
11731172
return v;
11741173
}
11751174

inspect.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ namespace Sass {
416416

417417
void Inspect::operator()(Number* n)
418418
{
419+
n->normalize();
419420
stringstream ss;
420421
ss.precision(ctx ? ctx->precision : 5);
421422
ss << fixed << n->value();

0 commit comments

Comments
 (0)