Как представить float в виде integer без потери информации?
Есть задача преобразования float значений в значения типа int без потери информации, т.е., чтобы потом можно было выполнить обратное преобразование. Как это можно реализовать? На уме только сериализация с помощью pickle.dumps, а затем получать int из этих байт, но мне кажется не совсем рациональным решением. И вряд ли оно будет удовлетворять требованиям ниже.
Очень важно, чтобы при таком преобразовании сохранялся порядок и сумма, т.е. для любых a, b: float должно выполняться:
to_float(to_int(a) + to_int(b)) == a + b
to_int(a) < to_int(b) при a < b и т.д.
Нашёл на англоязычном SO ответ, где побитово представляется структура float значений, но не представляю, как её можно применить для своей задачи.
Примечание: NaN и inf в моей задаче преобразовывать не надо.
Ответы (6 шт):
первый способ
хорошо то, что в программировании не бывает иррациональных чисел :), а значит любой float - рациональный
поэтому его можно представить в виде дроби - т.е. 2х чисел int
учитывая, что в питоне целочисленные значения не ограничены длинной, то в одном числе может быть 2 числа
например 0,5 - это [5][10],
можно реализовать такое хранение
второй способ
float очень легко переводятся в fixed - числа с фиксированной точкой - например если считать что первые n знаков - целая часть, а вторые m знаков - дробная часть, то
сложение и вычитание чисел будет ровно таким же как и у float или int
сравнение чисел будет ровно таким же как и у float или int
умножение будет чуть другим -
(a * b) >> m
перевод float в fixed:
fixed_value = 2**m * float_value
обратно соответственно
float_value = fixed_value / 2**m
Надо просто двоичное представление float'а переинтерпретировать в целое число.
На основе https://stackoverflow.com/a/14431225/4928642
import struct
def floatBits(f):
s = struct.pack('>f', f)
return struct.unpack('>l', s)[0]
На основе https://stackoverflow.com/a/58362843/4928642
import ctypes
def floatBits(x):
f = ctypes.c_float(x)
return ctypes.c_int.from_address(ctypes.addressof(f)).value
Данное преобразование не сохраняет сумму :(
lhs = random.getrandbits(30) rhs = random.getrandbits(30) assert int_to_float(lhs) + int_to_float(rhs) == int_to_float(lhs + rhs)
И не должно. И вообще не получится сделать так, чтобы сохраняло.
2 метода:
Из float в long
def floatToRawLongBits(f):
s = struct.pack('=f', f)
return struct.unpack('=l', s)[0]
Обратно из long в float
def longToRawFloatBits(l):
s = struct.pack('=l', l)
return struct.unpack('=f', s)[0]
Просьба сильно не бить, ногами, пока не силен в Python
В Java есть метод Float.floatToRawIntBits(), который битовое определение IEEE 754, представляет в виде битов целого числа. По его аналогии есть решение
А зря вы не хотите в двух целых числах хранить. Для этого в Python даже специальная библиотека есть. Которая может представить любое float число в виде точной дроби и работать с такими числами можно совершенно естественным образом:
from fractions import Fraction
print(Fraction(0.123456789))
# 8895999182988127/72057594037927936
print(float(Fraction(8895999182988127, 72057594037927936)))
# 0.123456789
to_float(to_int(a) + to_int(b)) == a + b to_int(a) < to_int(b) при a < b и т.д.
Я вспомнил, что в питоне int'ы бесконечные. У double есть 11 бит на степень, значит при умножении на 2**(2**12) получится целое число, которое можно положить в int. Только умножать надо по мере перевода, а не дробное число - иначе произойдёт переполнение.
Самое простое — просто умножать и делить на достаточно большой коэффициент, чтобы покрыть необходимый диапазон значений.
ratio = (1 << 156)
def to_int(f):
""" Если точности недостаточно, может вернуть 0 или бросить OverflowError при конвертации получившейся при умножении бесконечности
"""
return int(f * ratio)
def to_float(d):
return d / ratio
def to_int_2(f):
r = (f * ratio).as_integer_ratio()
assert r[1] == 1, 'Not enough precision'
return r[0]
Немного утверждений:
# На некоторых ratio мы таки будем получать получать ровно 0.3 при сложении, так что тут поведение нестабильно
assert to_float(to_int(0.1) + to_int(0.2)) == 0.1 + 0.2
assert to_float(to_int(-1.03e-30) + to_int(-2.04e-31)) == (-1.234e-30)
assert to_float(to_int(-1.03e+130) + to_int(-2.04e+129)) == (-1.234e+130)
assert to_int(1e-300) == 0
try:
print(to_int(1e+300))
except OverflowError as e:
assert str(e) == 'cannot convert float infinity to integer'
# to_int_2
assert to_float(to_int_2(0.1) + to_int_2(0.2)) == 0.1 + 0.2
assert to_float(to_int_2(-1.03e-30) + to_int_2(-2.04e-31)) == (-1.234e-30)
assert to_float(to_int_2(-1.03e+130) + to_int_2(-2.04e+129)) == (-1.234e+130)
try:
to_int_2(1e-300) == 0
except AssertionError as e:
assert str(e) == 'Not enough precision'
try:
print(to_int_2(1e+300))
except OverflowError as e:
assert str(e) == 'cannot convert Infinity to integer ratio'
Сейчас прихожу к выводу, что as_integer_ratio не нужен, достаточно проверки res == 0 and f != 0 в to_int.