Category Archives: Uncategorized

A Better Spaced Repetition Learning Algorithm: SM2+

Filed under Uncategorized

By far the most popular algorithm for Spaced Repetition is SM2.  Sites like Anki, Duolingo, Memrise, and Wanikani all chose SM2 over later SM-iterations (eg. SM15) because it is extremely simple, yet effective.  Despite that, it still has a number of glaring issues.  In this article I explain those issues, and provide a simple way to resolve them.

Original SM2 Algorithm

The algorithm determines which items the user must review every day.  To do that, it attaches the following data to every review-item:

  • easiness:float – A number  1.3 representing how easy the item is, with 1.3 being the hardest.  Defaults to 2.5
  • consecutiveCorrectAnswers:int – How many times in a row the user has correctly answered this item
  • nextDueDate:datetime – The next time this item needs to be reviewed

When the user wants to review, every item past its nextDueDate is queued.  After the user reviews an item, they (or the software) give themselves a performanceRating on a scale from 0-5 (0=worst, 5=best).  Set a cutoff for an answer being “correct” (defaults to 3). Then make the following changes to that item:

easiness += -0.8 + 0.28*performanceRating + 0.02*performanceRating^2

consecutiveCorrectAnswers = \begin{cases} consecutiveCorrectAnswers+1, \text{   if correct} \\ 0, \text{   if incorrect} \end{cases}

nextDueDate = \begin{cases} \text{now + $(6*easiness^{consecutiveCorrectAnswers-1})$ days}, \text{   if correct} \\ \text{now + 1 day}, \text{   if incorrect} \end{cases}

Problems with SM2

SM2 has a number of issues that limit its usefulness/effectiveness:

Problem: Non-generic variable ranges

The variable ranges are very specific to the original software, Supermemo.  easiness is a number  1.3, while performanceRating is an integer from [0,5]


Normalize easiness and performanceRating to [0,1].

This requires setting a max value for easiness, which I set to 3.0.  I also replaced easiness with difficulty, because it’s the more natural thing to measure.

Problem: Too many items per day

Because every day we do all the overdue items, it’s easy to encounter situations where one day you have only 5 items to review, and the next you have 50.


Only require doing the items that are the most overdue.  Use “percentage” of due-date, rather than number of days, so that 3 days overdue is severe if the review cooldown was 1 day, but not severe if it was 6 months.

This allows review sessions to be small and quick.  If the user has time, they can do multiple review sessions in a row.  As a bonus, this allows “almost overdue” items to be reviewed, if the user has time.

Problem: Overdue items all treated equally

If the user completes a month-overdue item correctly, it’s likely they know it pretty well, so showing it again in 6 days is not helpful.  They should get a bonus for correctly getting such overdue items correct.

Additionally, the above problem/solution allows almost-overdue items to be reviewed.  These items should not be given full credit.


Weight the changes in due-date and difficulty by the item’s percentage overdue.

Other adjustments

  • The quadratic term in the difficulty equation is so small it can be replaced with a simpler linear equation without adversely affecting the algorithm
  • Anki, Memrise, and others prefer an initial 3 days between due-dates, instead of 6.  I’ve adjusted the equations to use that preference.

The Modified “SM2+” Algorithm

Here is the new algorithm, with all the above improvements in place.

For each review-item, store the following data:

  • difficulty:float – How difficult the item is, from [0.0, 1.0].  Defaults to 0.3 (if the software has no way of determining a better default for an item)
  • daysBetweenReviews:float – How many days should occur between review attempts for this item
  • dateLastReviewed:datetime – The last time this item was reviewed

When the user wants to review items, choose the top 10~20 items, ordered descending by percentageOverdue (defined below), discarding items reviewed in the past 8 or so hours.

After an item is attempted, choose a performanceRating from [0.0, 1.0], with 1.0 being the best.  Set a cutoff point for the answer being “correct” (default is 0.6). Then set

percentOverdue = \begin{cases} Min(2, \frac{days(dateNow - dateLastReviewed)}{daysBetweenReviews}), \text{   if correct}\\ 1, \text{   if incorrect} \end{cases}

difficulty += percentOverdue*\frac{1}{17}(8-9*performanceRating)\text{, clamp to [0,1]}

difficultyWeight = 3-1.7*difficulty

daysBetweenReviews *= \begin{cases} 1+(difficultyWeight-1)*percentOverdue, \text{   if correct}\\ 1/difficultyWeight^2, \text{   if incorrect (min days = 1)} \end{cases}

Daily Review Sessions

The above algorithm determines which items to review, but how should you handle the actual review?

That’s the topic for my next post – stay tuned!

Using Vector3.Lerp() correctly in Unity

Filed under Uncategorized


The prototype for Vector3.Lerp looks like this



  • At percentage = 0.0, Lerp() returns start
  • At percentage = 1.0, Lerp() returns finish


Linear Interpolation Example


How to use Lerp correctly



How to use Lerp incorrectly









There are several problems with this.


The implementation of Lerp


Implementing Lerp() is actually remarkably simple:

(Edit Oct 2016: A commenter correctly pointed out that this implementation of Lerp() has floating-point rounding issues. See his comment for a more accurate version)

How fast are computers in everyday terms?

Filed under Uncategorized





Addition/subtraction1 second
If statement2 seconds
Integer multiplication5 seconds
Cache miss (L1 to L2)10 seconds
Function call††8-12 seconds
Virtual function call††12-14 seconds
If statement (branch prediction failure)20 seconds
Integer division17-28 seconds
Cache miss (L2 to L3)40 seconds
Cache miss (L3 to DRAM)2 minutes
SHA1 hash of 64-byte message15 minutes
Call to malloc() (rough estimate)30 minutes
Time for a speeding bullet to travel one inch3 days
Fastest Windows Timer Resolution (0.5ms)2 weeks
Fastest blip the human eye can see (2ms)2.25 months
Executing a large-ish SQL statement (3ms)3.5 months
Average human reaction time (220ms)21 years




Filed under Uncategorized




Branchless Conditionals (Compiler Optimization Technique)

Filed under Uncategorized





One possible improvement could be removing the conditional branch – the jne instruction.

Branch instructions are not in-and-of themselves particularly slow; all a jmp instruction does is write a value to the Program Counter (PC) register, and on a simple CPU which reads and executes one instruction at a time, jne wouldn’t be slower than any other instruction which writes to a register. However, modern CPUs are anything but simple.


And herein lies the problem with conditional jumps like jne: How does the processor know what the “next” instruction will be before it actually executes the conditional? The answer is: it doesn’t. It can guess (real-world programs are highly predictable, so Intel CPUs are able to guess correctly ~90% of the time), but if it guesses incorrectly, the entire Instruction Pipeline must be flushed: all the work the CPU did fetching and decoding the instructions it thought were going to be executed is invalidated and completely removed from the pipeline, and the entire pipeline sits around doing nothing while it waits for the jne instruction to complete. Essentially, a jne instruction with a failed prediction causes our fancy expensive pipelined CPU to act as though it were a boring old non-pipelined CPU.






So now the important question: Are branchless conditionals really faster?





Example from

Example from

Making Use of Spare Hard-Drives

Filed under Uncategorized

1+2+3+… = -1/12

Filed under Uncategorized
















Subtracting the bottom row from the top,


(Un)Common Questions about Electricity

Filed under Uncategorized




How to Give Someone Elf Ears and Vampire Fangs in Photoshop

Filed under Uncategorized



Those interested in her work can contact her here.

  1. Open your image in Photoshop










    And, after a few more tweaks, the final image:


Filed under Uncategorized



Replace All the Periods
Increase in size: 42.9%
Click OK, then hit "replace all."

Increase the Paragraph Spacing
Increase in size: 21.6%

Change the Font Size
Increase in size: 9.1%

Use a Different Font
Increase in size: 9.1%

Change the Margins
Increase in size: 7.2%

Change the Character Spacing
Increase in size: 7.1%

All effects put together:
Increase in size: 114.5% (over double!)