Decimal powers and memoization
jakob-e opened this issue · comments
Would it be possible to implement decimal powers to produce scales like these:
http://spencermortensen.com/articles/typographic-scale/
Maybe this could be done by switching to mathsass and perhaps considering memoization to speed things up a bit.
Tests:
memoization disabled
memoization enabled
Thanks and keep up the good work 👍
I’ve done a bit of testing with this a while back and performance is just bad, even in libsass. I may re-visit but until we get native pow()
in Sass I’m not convinced this is ready for production. Note that it works fine if you install via Compass because Compass has an actual pow()
function that I detect for and rout through.
At the moment, I’d rather push on Sass to include this much needed feature than to implement a sub-optimal work-around that may cause peoples projects to hang.
Pushing this off to 3.1 or only enabling via external lib. Complexities of this feature are getting in the way of other important 3.0 work.
Yup, you’ll be able to add this functionality easily in the next version by including a lib like mathsass, but not out of the box.
...three years later ;-)
If this is still on for a future version I found a super fast° pow function:
https://github.com/xi/sass-planifolia/edit/master/sass/math.scss
° fast of course is relative and depends on compiler. My tests on LibSass (CodeKit)
handles 10,000 calculations in about 3 seconds.
If you want to limit the implementation to only include pow here is a stripped down version:
$☠️--math-steps: 32 !default;
@function pow($x, $exponent, $steps: $☠️--math-steps) {
$exp1: round($exponent);
$exp2: $exponent - $exp1;
$pow1: ☠️--pow-int($x, $exp1);
@if $exp2 == 0 { @return $pow1; }
@else if $x == 0 and $exponent > 0 { @return 0; }
@else {
$y: ☠️--log($x, $steps) * $exp2;
$pow2: ☠️--exp-taylor-0($y, $steps);
@return $pow1 * $pow2;
}
}
// private helper functions
@function ☠️--log($x, $steps: $☠️--math-steps) {
$log10: 2.302585092994046;
$approx: ☠️--log10-approx($x);
// $y is in range [1, 10]
$y: $x / ☠️--pow-int(10, $approx);
@return $approx * $log10 + ☠️--log-taylor-1($y, $steps);
}
@function ☠️--pow-int($base, $exponent) {
@if $exponent < 0 { @return 1 / ☠️--pow-int($base, -$exponent); }
@else if $exponent == 0 { @return 1; }
@else if $exponent == 1 { @return $base; }
@else {
$exp: floor($exponent / 2);
$pow: ☠️--pow-int($base, $exp);
@if $exp * 2 == $exponent { @return $pow * $pow; }
@else { @return $pow * $pow * $base; }
}
}
@function ☠️--log10-approx($x) {
@if $x <= 0 { @error 'cannot calculate log of #{$x}'; }
// choose the smaller option (-1) because it yields better results in log().
@else if $x >= 1 { @return str-length(inspect(round($x))) - 1; }
@else { @return -1 * str-length(inspect(round(1 / $x))); }
}
@function ☠️--exp-taylor-0($x, $steps) {
$item: 1;
$result: 1;
@for $i from 1 to $steps {
$item: $item * $x / $i;
$result: $result + $item;
}
@return $result;
}
@function ☠️--log-taylor-1($x, $steps) {
$z: ($x - 1) / ($x + 1);
$power: $z;
$result: $z;
@for $i from 1 to $steps {
$power: $power * $z * $z;
$result: $result + $power / (2 * $i + 1);
}
@return 2 * $result;
}
Example (based on http://spencermortensen.com/articles/typographic-scale/)
golden-scale {
$r: 1.61803398874989484820; // ratio: phi
$n: 2; // notes: number of notes
@for $i from -5 through 5 {
scale-#{$i+5}: pow($r, $i/$n);
}
}
Output:
golden-scale {
scale-0: 0.300283106;
scale-1: 0.3819660113;
scale-2: 0.4858682718;
scale-3: 0.6180339887;
scale-4: 0.7861513778;
scale-5: 1;
scale-6: 1.2720196495;
scale-7: 1.6180339887;
scale-8: 2.0581710273;
scale-9: 2.6180339887;
scale-10: 3.3301906768;
}