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/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 BitArray.zeros(11)
46
- self.fraction = fraction if fraction is not None else BitArray.zeros(52)
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 = BitArray.from_float(value)
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 = BitArray([self.sign]) + self.exponent[:11] + self.fraction[:52]
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 = BitArray.ones(11)
111
- fraction = BitArray.ones(52)
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 = BitArray.ones(11)
124
- fraction = BitArray.zeros(52)
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 = BitArray.zeros(11)
135
- fraction = BitArray.zeros(52)
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 = BitArray.zeros(53) # 1 leading bit + 52 fraction bits
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 = BitArray.from_signed_int(exponent_self - 1, exp_result_length)
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 = BitArray.zeros(53)
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 = BitArray.from_signed_int(result_exponent - 1, exp_result_length)
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 = BitArray.zeros(106)
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 = BitArray.from_signed_int(result_exponent - 1, exp_result_length)
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 = BitArray.zeros(64) # Use a fixed size for consistency
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 = BitArray.from_signed_int(result_exponent - 1, exp_result_length)
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)