Skip to content

Commit 389bc92

Browse files
committed
Merge pull request #1049 from mgreter/bugfix/math-ops-with-units
Fix mathematical operations with different units
2 parents fc4b39e + b5ac186 commit 389bc92

File tree

5 files changed

+399
-145
lines changed

5 files changed

+399
-145
lines changed

ast.cpp

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,270 @@ namespace Sass {
589589
return result;
590590
}*/
591591

592+
Number::Number(ParserState pstate, double val, string u, bool zero)
593+
: Expression(pstate),
594+
value_(val),
595+
zero_(zero),
596+
numerator_units_(vector<string>()),
597+
denominator_units_(vector<string>()),
598+
hash_(0)
599+
{
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+
}
613+
concrete_type(NUMBER);
614+
}
615+
616+
string Number::unit() const
617+
{
618+
stringstream u;
619+
for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) {
620+
if (i) u << '*';
621+
u << numerator_units_[i];
622+
}
623+
if (!denominator_units_.empty()) u << '/';
624+
for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) {
625+
if (i) u << '*';
626+
u << denominator_units_[i];
627+
}
628+
return u.str();
629+
}
630+
631+
bool Number::is_unitless()
632+
{ return numerator_units_.empty() && denominator_units_.empty(); }
633+
634+
void Number::normalize(const string& prefered)
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;
683+
}
684+
}
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);
701+
}
702+
}
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);
790+
}
791+
}
792+
793+
// apply factor to value_
794+
// best precision this way
795+
value_ *= factor;
796+
797+
}
798+
799+
// useful for making one number compatible with another
800+
string Number::find_convertible_unit() const
801+
{
802+
for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) {
803+
string u(numerator_units_[i]);
804+
if (string_to_unit(u) != INCOMMENSURABLE) return u;
805+
}
806+
for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) {
807+
string u(denominator_units_[i]);
808+
if (string_to_unit(u) != INCOMMENSURABLE) return u;
809+
}
810+
return string();
811+
}
812+
813+
814+
bool Number::operator== (Expression* rhs) const
815+
{
816+
try
817+
{
818+
Number l(pstate_, value_, unit());
819+
Number& r = dynamic_cast<Number&>(*rhs);
820+
l.normalize(find_convertible_unit());
821+
r.normalize(find_convertible_unit());
822+
return l.unit() == r.unit() &&
823+
l.value() == r.value();
824+
}
825+
catch (std::bad_cast&) {}
826+
catch (...) { throw; }
827+
return false;
828+
}
829+
830+
bool Number::operator== (Expression& rhs) const
831+
{
832+
return operator==(&rhs);
833+
}
834+
835+
bool List::operator==(Expression* rhs) const
836+
{
837+
try
838+
{
839+
List* r = dynamic_cast<List*>(rhs);
840+
if (!r || length() != r->length()) return false;
841+
if (separator() != r->separator()) return false;
842+
for (size_t i = 0, L = r->length(); i < L; ++i)
843+
if (*elements()[i] != *(*r)[i]) return false;
844+
return true;
845+
}
846+
catch (std::bad_cast&) {}
847+
catch (...) { throw; }
848+
return false;
849+
}
850+
851+
bool List::operator== (Expression& rhs) const
852+
{
853+
return operator==(&rhs);
854+
}
855+
592856
Expression* Hashed::at(Expression* k) const
593857
{
594858
if (elements_.count(k))

0 commit comments

Comments
 (0)