0

I would like to write a function that accepts two numbers and their base (aka radix) and multiplies them.

base = int(input())
num1 = input()
num2 = input()

def multiply(num1: str, num2: str, base: int) -> str:
    pass

I tried to convert them to decimal and then multiply them but the precision can be lost.

For example:

2
101.1
101.1

is

11110.01
President James K. Polk
  • 40,516
  • 21
  • 95
  • 125
Parzival
  • 55
  • 6
  • https://en.wikipedia.org/wiki/Floating-point_error_mitigation – Keith Sep 25 '22 at 20:01
  • 1
    Generally, you can convert numbers to `float` without loosing precision if the base is a power of 2 (unless it doesn't fit into float on your machine). For other cases, you need a different solution. – Piotr Siupa Sep 25 '22 at 20:11
  • You could jest remove the `.` and threat the number as an integer, and after the calculation add the dot back to the resulting string at the right place (position of first dot + position of second dot, counting from the right). – Piotr Siupa Sep 25 '22 at 20:15
  • Consider using [decimal — Decimal fixed point and floating point arithmetic](https://docs.python.org/3/library/decimal.html) – AlanSTACK Sep 25 '22 at 20:17
  • 1
    @AlanSTACK I don't see there anything about it being able to handle bases other than 10. – Piotr Siupa Sep 25 '22 at 20:20
  • I had assumed the author was only concerned about base 10 - since his post mentions "decimal" specifically. – AlanSTACK Sep 25 '22 at 20:22

2 Answers2

2

The code saves positions of the .s (if they are present) and removes them. Strings are then converted to ints, multiplied and the result is converted back to string at which point a . is inserted at the appropriate position if necessary.

#!/usr/bin/env python

base = 2
num1 = '101.1'
num2 = '101.1'

def parsenum(num: str, base: int) -> (int, int):
    shift = len(num) - num.index('.') - 1 if '.' in num else 0
    num = num.replace('.', '')
    return int(num, base), shift

BS="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
def to_base(s, b):
    res = ""
    while s:
        res+=BS[s%b]
        s//= b
    return res[::-1] or "0"

def num2str(num: int, shift: int, base: int) -> str:
    string = to_base(abs(num), base)
    if shift:
        if len(string) <= shift:
            string = '0' * (shift - len(string) + 1) + string
        pos = len(string) - shift
        string = string[:pos] + '.' + string[pos:]
        string = string.rstrip('0').rstrip('.')
    if num < 0:
        string = '-' + string
    return string

def multiply(num1: str, num2: str, base: int) -> str:
    num1, shift1 = parsenum(num1, base)
    num2, shift2 = parsenum(num2, base)
    result_num, result_shift = num1 * num2, shift1 + shift2
    return num2str(result_num, result_shift, base)

print(multiply(num1, num2, base))

I'm 95% sure I've got all the corner cases, including handling negative numbers, stripping unnecessary 0s and removing trailing ..

The function to_base is from here: https://stackoverflow.com/a/53675480/3052438

Piotr Siupa
  • 3,929
  • 2
  • 29
  • 65
1
base = 2
s1, s2 = '101.1', '101.1'

d1 = ('.' in s1 and len(s1) - s1.index('.') - 1) or 0
d2 = ('.' in s2 and len(s2) - s2.index('.') - 1) or 0
a, b = int(s1.replace('.', ''), base), int(s2.replace('.', ''), base)
scale = base**(d1+d2)
intpart, fracpart = (a*b) // scale, (a*b) % scale
res = '%s.%s' % (baseN(intpart, base), baseN(fracpart, base).rjust(d1+d2, '0'))

res # '11110.01'

Probably some edge cases with the formatting left to do. The principle is 101.1 * 101.1 = (1011 * 2^-1) * (1011 * 2^-1) = 1011*1011 * 2^(-1 + -1) = 1111001 * 2^-2 = 11110.01

August
  • 12,410
  • 3
  • 35
  • 51