flexfloat 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flexfloat/__init__.py +22 -3
- flexfloat/bitarray/__init__.py +90 -0
- flexfloat/bitarray/bitarray.py +198 -0
- flexfloat/bitarray/bitarray_int64.py +308 -0
- flexfloat/{bitarray.py → bitarray/bitarray_list.py} +27 -97
- flexfloat/bitarray/bitarray_mixins.py +93 -0
- flexfloat/core.py +306 -20
- flexfloat-0.1.3.dist-info/METADATA +340 -0
- flexfloat-0.1.3.dist-info/RECORD +14 -0
- flexfloat-0.1.2.dist-info/METADATA +0 -147
- flexfloat-0.1.2.dist-info/RECORD +0 -10
- {flexfloat-0.1.2.dist-info → flexfloat-0.1.3.dist-info}/WHEEL +0 -0
- {flexfloat-0.1.2.dist-info → flexfloat-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {flexfloat-0.1.2.dist-info → flexfloat-0.1.3.dist-info}/top_level.txt +0 -0
flexfloat/core.py
CHANGED
@@ -3,9 +3,9 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
import math
|
6
|
-
from typing import Final
|
6
|
+
from typing import ClassVar, Final
|
7
7
|
|
8
|
-
from .bitarray import BitArray
|
8
|
+
from .bitarray import BitArray, BitArrayType
|
9
9
|
from .types import Number
|
10
10
|
|
11
11
|
LOG10_2: Final[float] = math.log10(2)
|
@@ -28,6 +28,8 @@ class FlexFloat:
|
|
28
28
|
(mantissa) of the number.
|
29
29
|
"""
|
30
30
|
|
31
|
+
e: ClassVar[FlexFloat]
|
32
|
+
|
31
33
|
def __init__(
|
32
34
|
self,
|
33
35
|
sign: bool = False,
|
@@ -42,8 +44,8 @@ class FlexFloat:
|
|
42
44
|
fraction (BitArray | None): The fraction bit array (If None, represents 0).
|
43
45
|
"""
|
44
46
|
self.sign = sign
|
45
|
-
self.exponent = exponent if exponent is not None else
|
46
|
-
self.fraction = fraction if fraction is not None else
|
47
|
+
self.exponent = exponent if exponent is not None else BitArrayType.zeros(11)
|
48
|
+
self.fraction = fraction if fraction is not None else BitArrayType.zeros(52)
|
47
49
|
|
48
50
|
@classmethod
|
49
51
|
def from_float(cls, value: Number) -> FlexFloat:
|
@@ -55,7 +57,7 @@ class FlexFloat:
|
|
55
57
|
FlexFloat: A new FlexFloat instance representing the number.
|
56
58
|
"""
|
57
59
|
value = float(value)
|
58
|
-
bits =
|
60
|
+
bits = BitArrayType.from_float(value)
|
59
61
|
|
60
62
|
return cls(sign=bits[0], exponent=bits[1:12], fraction=bits[12:64])
|
61
63
|
|
@@ -72,7 +74,7 @@ class FlexFloat:
|
|
72
74
|
if len(self.exponent) < 11 or len(self.fraction) < 52:
|
73
75
|
raise ValueError("Must be a standard 64-bit FlexFloat")
|
74
76
|
|
75
|
-
bits =
|
77
|
+
bits = BitArrayType([self.sign]) + self.exponent[:11] + self.fraction[:52]
|
76
78
|
return bits.to_float()
|
77
79
|
|
78
80
|
def __repr__(self) -> str:
|
@@ -107,8 +109,8 @@ class FlexFloat:
|
|
107
109
|
Returns:
|
108
110
|
FlexFloat: A new FlexFloat instance representing NaN.
|
109
111
|
"""
|
110
|
-
exponent =
|
111
|
-
fraction =
|
112
|
+
exponent = BitArrayType.ones(11)
|
113
|
+
fraction = BitArrayType.ones(52)
|
112
114
|
return cls(sign=True, exponent=exponent, fraction=fraction)
|
113
115
|
|
114
116
|
@classmethod
|
@@ -120,8 +122,8 @@ class FlexFloat:
|
|
120
122
|
Returns:
|
121
123
|
FlexFloat: A new FlexFloat instance representing Infinity.
|
122
124
|
"""
|
123
|
-
exponent =
|
124
|
-
fraction =
|
125
|
+
exponent = BitArrayType.ones(11)
|
126
|
+
fraction = BitArrayType.zeros(52)
|
125
127
|
return cls(sign=sign, exponent=exponent, fraction=fraction)
|
126
128
|
|
127
129
|
@classmethod
|
@@ -131,8 +133,8 @@ class FlexFloat:
|
|
131
133
|
Returns:
|
132
134
|
FlexFloat: A new FlexFloat instance representing zero.
|
133
135
|
"""
|
134
|
-
exponent =
|
135
|
-
fraction =
|
136
|
+
exponent = BitArrayType.zeros(11)
|
137
|
+
fraction = BitArrayType.zeros(52)
|
136
138
|
return cls(sign=False, exponent=exponent, fraction=fraction)
|
137
139
|
|
138
140
|
def _is_special_exponent(self) -> bool:
|
@@ -224,6 +226,7 @@ class FlexFloat:
|
|
224
226
|
while normalized_mantissa >= 10.0:
|
225
227
|
normalized_mantissa /= 10.0
|
226
228
|
decimal_exponent += 1
|
229
|
+
|
227
230
|
while normalized_mantissa < 1.0:
|
228
231
|
normalized_mantissa *= 10.0
|
229
232
|
decimal_exponent -= 1
|
@@ -332,7 +335,7 @@ class FlexFloat:
|
|
332
335
|
f"got {len(mantissa_other)} bits."
|
333
336
|
)
|
334
337
|
|
335
|
-
mantissa_result =
|
338
|
+
mantissa_result = BitArrayType.zeros(53) # 1 leading bit + 52 fraction bits
|
336
339
|
carry = False
|
337
340
|
for i in range(52, -1, -1):
|
338
341
|
total = mantissa_self[i] + mantissa_other[i] + carry
|
@@ -352,7 +355,9 @@ class FlexFloat:
|
|
352
355
|
exponent_self - (1 << (exp_result_length - 1)) < 2
|
353
356
|
), "Exponent growth should not exceed 1 bit."
|
354
357
|
|
355
|
-
exponent_result =
|
358
|
+
exponent_result = BitArrayType.from_signed_int(
|
359
|
+
exponent_self - 1, exp_result_length
|
360
|
+
)
|
356
361
|
return FlexFloat(
|
357
362
|
sign=self.sign,
|
358
363
|
exponent=exponent_result,
|
@@ -450,7 +455,7 @@ class FlexFloat:
|
|
450
455
|
f"got {len(smaller_mantissa)} bits."
|
451
456
|
)
|
452
457
|
|
453
|
-
mantissa_result =
|
458
|
+
mantissa_result = BitArrayType.zeros(53)
|
454
459
|
borrow = False
|
455
460
|
for i in range(52, -1, -1):
|
456
461
|
diff = int(larger_mantissa[i]) - int(smaller_mantissa[i]) - int(borrow)
|
@@ -478,7 +483,9 @@ class FlexFloat:
|
|
478
483
|
# Step 7: Grow exponent if necessary (handle underflow)
|
479
484
|
exp_result_length = self._grow_exponent(result_exponent, len(self.exponent))
|
480
485
|
|
481
|
-
exp_result =
|
486
|
+
exp_result = BitArrayType.from_signed_int(
|
487
|
+
result_exponent - 1, exp_result_length
|
488
|
+
)
|
482
489
|
|
483
490
|
return FlexFloat(
|
484
491
|
sign=result_sign,
|
@@ -552,7 +559,7 @@ class FlexFloat:
|
|
552
559
|
if product == 0:
|
553
560
|
return FlexFloat.zero()
|
554
561
|
|
555
|
-
product_bits =
|
562
|
+
product_bits = BitArrayType.zeros(106)
|
556
563
|
for i in range(105, -1, -1):
|
557
564
|
product_bits[i] = product & 1 == 1
|
558
565
|
product >>= 1
|
@@ -584,7 +591,9 @@ class FlexFloat:
|
|
584
591
|
# Check if we need to grow the exponent to accommodate the result
|
585
592
|
exp_result_length = self._grow_exponent(result_exponent, exp_result_length)
|
586
593
|
|
587
|
-
exp_result =
|
594
|
+
exp_result = BitArrayType.from_signed_int(
|
595
|
+
result_exponent - 1, exp_result_length
|
596
|
+
)
|
588
597
|
|
589
598
|
return FlexFloat(
|
590
599
|
sign=result_sign,
|
@@ -682,7 +691,7 @@ class FlexFloat:
|
|
682
691
|
return FlexFloat.zero()
|
683
692
|
|
684
693
|
# Convert quotient to BitArray for easier bit manipulation
|
685
|
-
quotient_bitarray =
|
694
|
+
quotient_bitarray = BitArrayType.zeros(64) # Use a fixed size for consistency
|
686
695
|
temp_quotient = quotient
|
687
696
|
bit_pos = 63
|
688
697
|
while temp_quotient > 0 and bit_pos >= 0:
|
@@ -709,7 +718,9 @@ class FlexFloat:
|
|
709
718
|
# Check if we need to grow the exponent to accommodate the result
|
710
719
|
exp_result_length = self._grow_exponent(result_exponent, exp_result_length)
|
711
720
|
|
712
|
-
exp_result =
|
721
|
+
exp_result = BitArrayType.from_signed_int(
|
722
|
+
result_exponent - 1, exp_result_length
|
723
|
+
)
|
713
724
|
|
714
725
|
return FlexFloat(
|
715
726
|
sign=result_sign,
|
@@ -726,3 +737,278 @@ class FlexFloat:
|
|
726
737
|
FlexFloat: A new FlexFloat instance representing the quotient.
|
727
738
|
"""
|
728
739
|
return FlexFloat.from_float(other) / self
|
740
|
+
|
741
|
+
def __abs__(self) -> FlexFloat:
|
742
|
+
"""Return the absolute value of the FlexFloat instance.
|
743
|
+
|
744
|
+
Returns:
|
745
|
+
FlexFloat: A new FlexFloat instance with the same exponent and fraction,
|
746
|
+
but with the sign set to False (positive).
|
747
|
+
"""
|
748
|
+
return FlexFloat(
|
749
|
+
sign=False,
|
750
|
+
exponent=self.exponent.copy(),
|
751
|
+
fraction=self.fraction.copy(),
|
752
|
+
)
|
753
|
+
|
754
|
+
def abs(self) -> FlexFloat:
|
755
|
+
"""Calculate the absolute value of the FlexFloat instance.
|
756
|
+
|
757
|
+
Returns:
|
758
|
+
FlexFloat: A new FlexFloat instance with the same exponent and fraction,
|
759
|
+
but with the sign set to False (positive).
|
760
|
+
"""
|
761
|
+
return abs(self)
|
762
|
+
|
763
|
+
def exp(self) -> FlexFloat:
|
764
|
+
"""Calculate the exponential of the FlexFloat instance.
|
765
|
+
|
766
|
+
Returns:
|
767
|
+
FlexFloat: A new FlexFloat instance representing e raised to the power
|
768
|
+
of this instance.
|
769
|
+
"""
|
770
|
+
# Use Python's math.exp for the base calculation
|
771
|
+
return FlexFloat.e**self
|
772
|
+
|
773
|
+
def __pow__(self, other: FlexFloat | Number) -> FlexFloat:
|
774
|
+
"""Power operation for FlexFloat instances.
|
775
|
+
|
776
|
+
Args:
|
777
|
+
other (FlexFloat | Number): The exponent.
|
778
|
+
Returns:
|
779
|
+
FlexFloat: A new FlexFloat instance representing the power.
|
780
|
+
"""
|
781
|
+
if isinstance(other, Number):
|
782
|
+
other = FlexFloat.from_float(other)
|
783
|
+
if not isinstance(other, FlexFloat):
|
784
|
+
raise TypeError("Can only raise FlexFloat instances to a power.")
|
785
|
+
|
786
|
+
# OBJECTIVE: Compute self^other using the identity: a^b = exp(b * ln(a))
|
787
|
+
# For most cases, we use logarithmic computation for generality
|
788
|
+
# Handle special cases first for performance and correctness
|
789
|
+
#
|
790
|
+
# Steps:
|
791
|
+
# 0. Handle special cases (NaN, infinity, zero, one)
|
792
|
+
# 1. Handle negative base with fractional exponent (returns NaN)
|
793
|
+
# 2. For general case: compute ln(|base|) * exponent
|
794
|
+
# 3. Compute exp(result) to get the final power
|
795
|
+
# 4. Handle sign for negative base with integer exponent
|
796
|
+
# 5. Return new FlexFloat instance
|
797
|
+
|
798
|
+
# Step 0: Handle special cases
|
799
|
+
if self.is_nan() or other.is_nan():
|
800
|
+
return FlexFloat.nan()
|
801
|
+
|
802
|
+
# Zero base cases
|
803
|
+
if self.is_zero():
|
804
|
+
if other.is_zero():
|
805
|
+
return FlexFloat.from_float(1.0) # 0^0 = 1 (by convention)
|
806
|
+
if other.sign: # negative exponent
|
807
|
+
return FlexFloat.infinity(sign=False) # 0^(-n) = +infinity
|
808
|
+
return FlexFloat.zero() # 0^n = 0 for positive n
|
809
|
+
|
810
|
+
# One base: 1^anything = 1
|
811
|
+
if (
|
812
|
+
not self.sign
|
813
|
+
and self.exponent.to_signed_int() == -1
|
814
|
+
and not any(self.fraction)
|
815
|
+
):
|
816
|
+
# Check if self is exactly 1.0 (exponent = -1, fraction = 0)
|
817
|
+
return FlexFloat.from_float(1.0)
|
818
|
+
|
819
|
+
# Zero exponent: anything^0 = 1 (except 0^0 handled above)
|
820
|
+
if other.is_zero():
|
821
|
+
return FlexFloat.from_float(1.0)
|
822
|
+
|
823
|
+
# One exponent: anything^1 = anything
|
824
|
+
if (
|
825
|
+
not other.sign
|
826
|
+
and other.exponent.to_signed_int() == -1
|
827
|
+
and not any(other.fraction)
|
828
|
+
):
|
829
|
+
# Check if other is exactly 1.0 (exponent = -1, fraction = 0)
|
830
|
+
return self.copy()
|
831
|
+
|
832
|
+
# Infinity cases
|
833
|
+
if self.is_infinity():
|
834
|
+
if other.is_zero():
|
835
|
+
return FlexFloat.from_float(1.0) # inf^0 = 1
|
836
|
+
if other.sign: # negative exponent
|
837
|
+
return FlexFloat.zero() # inf^(-n) = 0
|
838
|
+
# inf^n = inf, but need to handle sign for negative infinity
|
839
|
+
if not self.sign: # positive infinity
|
840
|
+
return FlexFloat.infinity(sign=False) # (+inf)^n = +inf
|
841
|
+
|
842
|
+
# Check if exponent is an integer for sign determination
|
843
|
+
try:
|
844
|
+
exp_float = other.to_float()
|
845
|
+
if exp_float == int(exp_float): # integer exponent
|
846
|
+
result_sign = int(exp_float) % 2 == 1 # odd = negative
|
847
|
+
else:
|
848
|
+
return FlexFloat.nan() # (-inf)^(non-integer) = NaN
|
849
|
+
except (ValueError, OverflowError):
|
850
|
+
# For very large exponents, assume positive result
|
851
|
+
result_sign = False
|
852
|
+
return FlexFloat.infinity(sign=result_sign)
|
853
|
+
|
854
|
+
if other.is_infinity():
|
855
|
+
try:
|
856
|
+
base_abs = abs(self.to_float())
|
857
|
+
if base_abs == 1.0:
|
858
|
+
return FlexFloat.from_float(1.0) # 1^inf = 1
|
859
|
+
if base_abs > 1.0:
|
860
|
+
return (
|
861
|
+
FlexFloat.infinity(sign=False)
|
862
|
+
if not other.sign
|
863
|
+
else FlexFloat.zero()
|
864
|
+
)
|
865
|
+
# base_abs < 1.0
|
866
|
+
return (
|
867
|
+
FlexFloat.zero()
|
868
|
+
if not other.sign
|
869
|
+
else FlexFloat.infinity(sign=False)
|
870
|
+
)
|
871
|
+
except (ValueError, OverflowError):
|
872
|
+
# For very large/small bases, use log-based approach
|
873
|
+
pass
|
874
|
+
|
875
|
+
# Step 1: Handle negative base with fractional exponent
|
876
|
+
if self.sign:
|
877
|
+
try:
|
878
|
+
# Check if exponent is an integer
|
879
|
+
exp_float = other.to_float()
|
880
|
+
if exp_float != int(exp_float):
|
881
|
+
return FlexFloat.nan() # (-base)^(non-integer) = NaN
|
882
|
+
is_odd_exponent = int(exp_float) % 2 == 1
|
883
|
+
except (ValueError, OverflowError):
|
884
|
+
# For very large exponents, we can't easily determine if integer
|
885
|
+
# Conservative approach: return NaN for negative base
|
886
|
+
return FlexFloat.nan()
|
887
|
+
else:
|
888
|
+
is_odd_exponent = False
|
889
|
+
|
890
|
+
# Step 2-3: General case using a^b = exp(b * ln(a))
|
891
|
+
# We need to compute ln(|self|) * other, then exp of that result
|
892
|
+
|
893
|
+
# For the logarithmic approach, we work with the absolute value
|
894
|
+
abs_base = self.abs()
|
895
|
+
|
896
|
+
# Use the mathematical identity: a^b = exp(b * ln(a))
|
897
|
+
# However, since we don't have ln implemented, we'll use Python's math
|
898
|
+
# for the core calculation and then convert back to FlexFloat
|
899
|
+
try:
|
900
|
+
# Convert to float for the mathematical computation
|
901
|
+
base_float = abs_base.to_float()
|
902
|
+
exp_float = other.to_float()
|
903
|
+
|
904
|
+
# For very small results that would underflow in float arithmetic,
|
905
|
+
# we need to use extended precision
|
906
|
+
log_result_estimate = exp_float * math.log(base_float)
|
907
|
+
|
908
|
+
# Check if the result would be too small for standard float
|
909
|
+
if log_result_estimate < -700: # This would underflow to 0 in float
|
910
|
+
# Use extended precision approach
|
911
|
+
# Estimate the required exponent bits for the very small result
|
912
|
+
estimated_exp = int(log_result_estimate / LOG10_2)
|
913
|
+
required_exp_bits = max(11, abs(estimated_exp).bit_length() + 2)
|
914
|
+
|
915
|
+
# Create a small result with extended exponent
|
916
|
+
small_exp = BitArrayType.from_signed_int(
|
917
|
+
estimated_exp, required_exp_bits
|
918
|
+
)
|
919
|
+
# Use a normalized mantissa (leading 1 + some fraction bits)
|
920
|
+
result = FlexFloat(
|
921
|
+
sign=False, exponent=small_exp, fraction=BitArrayType.ones(52)
|
922
|
+
)
|
923
|
+
else:
|
924
|
+
# Compute the power using Python's built-in pow
|
925
|
+
result_float = pow(base_float, exp_float)
|
926
|
+
|
927
|
+
# Handle potential overflow/underflow
|
928
|
+
if math.isinf(result_float):
|
929
|
+
return FlexFloat.infinity(sign=False)
|
930
|
+
if result_float == 0.0:
|
931
|
+
# Even if Python returns 0, we might want extended precision
|
932
|
+
if log_result_estimate < -300: # Very small but not quite underflow
|
933
|
+
estimated_exp = int(log_result_estimate / LOG10_2)
|
934
|
+
required_exp_bits = max(11, abs(estimated_exp).bit_length() + 2)
|
935
|
+
small_exp = BitArrayType.from_signed_int(
|
936
|
+
estimated_exp, required_exp_bits
|
937
|
+
)
|
938
|
+
result = FlexFloat(
|
939
|
+
sign=False,
|
940
|
+
exponent=small_exp,
|
941
|
+
fraction=BitArrayType.ones(52),
|
942
|
+
)
|
943
|
+
else:
|
944
|
+
return FlexFloat.zero()
|
945
|
+
if math.isnan(result_float):
|
946
|
+
return FlexFloat.nan()
|
947
|
+
|
948
|
+
# Convert result back to FlexFloat
|
949
|
+
result = FlexFloat.from_float(result_float)
|
950
|
+
|
951
|
+
except (ValueError, OverflowError):
|
952
|
+
# If float computation overflows, use extended precision approach
|
953
|
+
# This is a fallback for when the result is too large for float
|
954
|
+
|
955
|
+
# For very large results, we estimate the exponent growth needed
|
956
|
+
try:
|
957
|
+
# Estimate log(result) = exponent * log(base)
|
958
|
+
log_base = (
|
959
|
+
math.log(abs(self.to_float()))
|
960
|
+
if not self.is_zero()
|
961
|
+
else -float("inf")
|
962
|
+
)
|
963
|
+
exp_float = other.to_float()
|
964
|
+
estimated_log_result = exp_float * log_base
|
965
|
+
|
966
|
+
# Estimate the required exponent bits
|
967
|
+
estimated_exp_float = estimated_log_result / LOG10_2
|
968
|
+
required_exp_bits = max(
|
969
|
+
11, int(abs(estimated_exp_float)).bit_length() + 2
|
970
|
+
)
|
971
|
+
|
972
|
+
# Create a result with extended exponent
|
973
|
+
# This is a simplified approach - a full implementation would
|
974
|
+
# use arbitrary precision arithmetic
|
975
|
+
if estimated_exp_float > 0:
|
976
|
+
# Very large positive result
|
977
|
+
large_exp = BitArrayType.from_signed_int(
|
978
|
+
int(estimated_exp_float), required_exp_bits
|
979
|
+
)
|
980
|
+
result = FlexFloat(
|
981
|
+
sign=False, exponent=large_exp, fraction=BitArrayType.ones(52)
|
982
|
+
)
|
983
|
+
else:
|
984
|
+
# Very small positive result
|
985
|
+
small_exp = BitArrayType.from_signed_int(
|
986
|
+
int(estimated_exp_float), required_exp_bits
|
987
|
+
)
|
988
|
+
result = FlexFloat(
|
989
|
+
sign=False, exponent=small_exp, fraction=BitArrayType.ones(52)
|
990
|
+
)
|
991
|
+
|
992
|
+
except (ValueError, OverflowError, ZeroDivisionError):
|
993
|
+
# Ultimate fallback
|
994
|
+
return FlexFloat.nan()
|
995
|
+
|
996
|
+
# Step 4: Handle sign for negative base with integer exponent
|
997
|
+
if self.sign and is_odd_exponent:
|
998
|
+
result.sign = True
|
999
|
+
|
1000
|
+
return result
|
1001
|
+
|
1002
|
+
def __rpow__(self, base: Number) -> FlexFloat:
|
1003
|
+
"""Right-hand power operation for Number types.
|
1004
|
+
|
1005
|
+
Args:
|
1006
|
+
base (float | int): The base to raise to the power of this FlexFloat.
|
1007
|
+
Returns:
|
1008
|
+
FlexFloat: A new FlexFloat instance representing the power.
|
1009
|
+
"""
|
1010
|
+
return FlexFloat.from_float(base) ** self
|
1011
|
+
|
1012
|
+
|
1013
|
+
# Initialize class variable after class definition
|
1014
|
+
FlexFloat.e = FlexFloat.from_float(math.e)
|