230 lines
5.3 KiB
Python
Executable File
230 lines
5.3 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# Copyright © 2022 Simon Forman
|
|
#
|
|
# This file is part of Thun
|
|
#
|
|
# Thun is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Thun is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Thun. If not see <http://www.gnu.org/licenses/>.
|
|
#
|
|
'''
|
|
Let's grok division (and modulus.)
|
|
|
|
For now we will deal with both positive
|
|
(or at least of the same sign.)
|
|
|
|
|
|
'''
|
|
from random import randint
|
|
|
|
|
|
def div_mod(A, B):
|
|
'''
|
|
A and B are lists of digits, LSB->MSB.
|
|
'''
|
|
if not A:
|
|
return [], []
|
|
if not B:
|
|
raise ZeroDivisionError()
|
|
a_len, b_len = len(A), len(B)
|
|
if -1 == cmp_digits(A, B): # A < B
|
|
return [], A
|
|
|
|
# Whew! Okay, we got all that out of the way.
|
|
# A > B
|
|
|
|
A, A_digits = A[:-b_len], A[-b_len:]
|
|
if -1 == cmp_digits(A_digits, B):
|
|
# Because we know
|
|
# A > B AND a_len >= b_len (aka len(A_digits))
|
|
# if A_digits < B there must be at least one more
|
|
# digit in A:
|
|
assert a_len > b_len
|
|
A_digits.insert(0, A.pop())
|
|
|
|
assert -1 < cmp_digits(A_digits, B) # A_digits >= B
|
|
|
|
q, R = lil_divmod(A_digits, B)
|
|
|
|
# So we have divided a prefix of A by B
|
|
# resulting in a digit q of the answer Q
|
|
# and a remainder R that must be extended
|
|
# with the more digits of A to make a new
|
|
# number N >= B
|
|
# what I want to do here is...
|
|
'''
|
|
|
|
___2___
|
|
72)145000
|
|
-144 = 72 * 2
|
|
---
|
|
1
|
|
|
|
B = 72
|
|
A_digits = 145
|
|
A = 000
|
|
q = 2
|
|
R = 1
|
|
|
|
'''
|
|
Q, Remainder = foo(A, B, R)
|
|
Q.append(q)
|
|
return Q, Remainder
|
|
|
|
|
|
def foo(Digits, Divisor, Prefix):
|
|
'''E.g.:
|
|
|
|
___2___
|
|
72)145000
|
|
-144 = 72 * 2
|
|
---
|
|
1
|
|
|
|
Digits = 000
|
|
Divisor = 72
|
|
Prefix = 1
|
|
'''
|
|
|
|
Quotient = []
|
|
while Digits:
|
|
Prefix.insert(0, Digits.pop())
|
|
if -1 < cmp_digits(Prefix, Divisor): # Prefix >= Divisor
|
|
break
|
|
Quotient.insert(0, 0)
|
|
|
|
'''
|
|
One iteration through the while loop:
|
|
|
|
___20__
|
|
72)145000
|
|
-144| = 72 * 2
|
|
---|
|
|
10
|
|
|
|
Another iteration through the while loop:
|
|
|
|
___20__
|
|
72)145000
|
|
-144|| = 72 * 2
|
|
---||
|
|
100 >= 72
|
|
|
|
Digits = 0
|
|
Divisor = 72 (still)
|
|
Prefix = 100
|
|
|
|
At this point Prefix >= Divisor OR Digits == [] OR BOTH
|
|
|
|
if Prefix < Divisor AND not Digits:
|
|
the remainder is Prefix
|
|
return Quotient, Prefix
|
|
'''
|
|
|
|
if -1 == cmp_digits(Prefix, Divisor) and not Digits:
|
|
return Quotient, Prefix
|
|
|
|
'''
|
|
if Prefix < Divisor AND Digits:
|
|
Can't get here
|
|
'''
|
|
|
|
assert -1 < cmp_digits(Prefix, Divisor) # Prefix >= Divisor
|
|
|
|
q, Remainder = lil_divmod(Prefix, Divisor)
|
|
Quotient.insert(0, q)
|
|
|
|
'''
|
|
___20q_
|
|
72)145000
|
|
-144|| = 72 * 2
|
|
---||
|
|
100 >= 72
|
|
N = 72 * q
|
|
---
|
|
28 = (100 - N) = (100 - 72q)
|
|
|
|
Remainder = 28
|
|
New_Prefix = Remainder
|
|
Digits = 0 (still)
|
|
Divisor = 72 (still)
|
|
'''
|
|
|
|
if Digits:
|
|
Q, Remainder = foo(Digits, Divisor, Remainder)
|
|
Quotient = Q + Quotient
|
|
return Quotient, Remainder
|
|
|
|
|
|
def lil_divmod(A, B):
|
|
assert -1 < cmp_digits(A, B) # A >= B
|
|
assert A and B
|
|
# There is a greatest digit in 1..9 such that:
|
|
# B * digit <= A
|
|
# The obvious thing to do here is a bisect search,
|
|
# if we were really just doing 1..9 we could go linear.
|
|
# Maybe drive it by the bits in digit?
|
|
digit = 9
|
|
Q = mul_digit_by_list_of_digits(digit, B)
|
|
while 1 == cmp_digits(Q, A): # Q > A
|
|
digit = digit - 1
|
|
if not digit:
|
|
raise ValueError('huh?')
|
|
Q = mul_digit_by_list_of_digits(digit, B)
|
|
assert -1 < cmp_digits(A, Q)
|
|
assert list_to_int(A) >= list_to_int(Q)
|
|
remainder = subtract(A, Q)
|
|
assert -1 == cmp_digits(remainder, B) # Remainder < Divisor
|
|
return digit, remainder
|
|
|
|
def mul_digit_by_list_of_digits(digit, A):
|
|
assert 0 <= digit <= 9
|
|
for n in A:
|
|
assert 0 <= n <= 9
|
|
return int_to_list(list_to_int(A) * digit)
|
|
|
|
def int_to_list(i):
|
|
assert i >= 0
|
|
if not i: return []
|
|
return list(map(int, str(i)[::-1]))
|
|
|
|
def list_to_int(A):
|
|
if not A: return 0
|
|
i = int(''.join(map(str, A[::-1])))
|
|
assert i >= 0
|
|
return i
|
|
|
|
def cmp_digits(A, B):
|
|
a, b = list_to_int(A), list_to_int(B)
|
|
return 1 if a > b else 0 if a == b else -1
|
|
|
|
def subtract(A, B):
|
|
return int_to_list(list_to_int(A) - list_to_int(B))
|
|
|
|
def try_it(a, b):
|
|
A = int_to_list(a)
|
|
B = int_to_list(b)
|
|
print(f'divmod({list_to_int(A)}, {list_to_int(B)}) = ', end='')
|
|
Q, R = div_mod(A, B)
|
|
q, r = divmod(a, b)
|
|
assert q == list_to_int(Q)
|
|
assert r == list_to_int(R)
|
|
print(f'{list_to_int(Q)}, {list_to_int(R)}')
|
|
|
|
for _ in range(10**6):
|
|
try_it(
|
|
randint(0, 10**randint(8, 15)),
|
|
randint(1, 10**randint(1, 15))
|
|
)
|