#!/usr/bin/python
# -*- coding: utf-8 -*-
"""Generate CSV file of Briggsian logarithms to import into Anki.
The idea is to come up with a minimal set of logarithms to memorize in
order to be able to calculate a product to within a given precision;
10% worst-case precision spaces the mantissas about 10% apart, and
that gives you only about 30 logarithms to memorize. And if you
memorize them to three places, you should be able to linearly
interpolate to get better than 1% precision.
This is turning out to be a harder problem than I had expected,
because I discovered a hidden requirement in my subconscious that the
numbers not skip over round numbers. So, for example, increasing by
10% from 1.0, we get to 1.1, 1.2, 1.3, 1.4, 1.5, 1.7, 1.9, and then
2.1, skipping over 2! (Also note that 1.7 is more than 10% high from
1.5.)
So my strategy is to recursively subdivide the interval at a given
precision. First subdivide the interval from 1 to 10 into 1, 2, 3,
etc., which differ by more than 10%; then divide the interval from 1
to 2 as evenly as possible into 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.7, and
1.8; and then if we’re doing more digits of precision, subdivide each
of those intervals to hit our desired precision. So for example in
between 1.5 and 1.7 we would get 1.6, really 1.59687 or so.
This is probably the best way to rule a slide rule as well.
But this code doesn’t work yet; I need to rethink it.
"""
import csv
import math
import sys
def subdivide(start, stop, spacing):
"""Yield numbers in [start, stop) equally exponentially spaced by spacing.
spacing --- the worst-case relative error, like 0.1 for 10%.
start --- the first number to yield.
stop --- the first number that would follow the returned sequence.
In general the numbers yielded will be spaced somewhat closer than
spacing in order to evenly divide the interval between start and
stop.
"""
interval_size_log = math.log(stop) - math.log(start)
n_divisions = int(math.ceil(interval_size_log / math.log(1 + spacing)))
return ((start * math.exp(i * interval_size_log / n_divisions)
for i in range(n_divisions)))
def recursively_subdivide(start, stop, precision, spacing):
print start, stop, precision, round(start, precision), round(stop, precision)
if round(start, precision) == round(stop, precision):
return subdivide(start, stop, spacing)
rv = []
recursion = recursively_subdivide(start, stop, precision-1, spacing)
for substart, substop in pairs(recursion + [stop]):
rv.extend(recursively_subdivide(substart, substop, precision, spacing))
return rv
def pairs(seq):
seq = iter(seq)
last = seq.next()
for item in seq:
yield last, item
last = item
# This still doesn’t work either. It doesn’t jump far enough.
def iteratively_subdivide(start, stop, precision, spacing):
i = start
while i < stop:
yield i
for size in range(precision+1):
next_i = round(i + 10**-size, size)
if size == precision or next_i <= i * (1 + spacing):
break
i = next_i
def writelogs(output, spacing, mprecision, lprecision):
mantissa = 1.0
while mantissa < 10.0:
output.writerow(('%.*f' % (mprecision, mantissa),
'%0.*f' % (lprecision, math.log10(mantissa))))
new_mantissa = round(max(mantissa * (1 + spacing),
mantissa + 10**-mprecision), mprecision)
# If we’ve jumped past a value that we should include, include it
candidate = round(new_mantissa, mprecision-1)
if mantissa < candidate < new_mantissa:
new_mantissa = candidate
mantissa = new_mantissa
if __name__ == '__main__':
writelogs(csv.writer(sys.stdout), spacing=0.1, mprecision=3, lprecision=3)