Repository: polkascan/py-scale-codec

Issue:
Support for F32 & F64
https://github.com/polkadot-js/api/pull/4958

Discussion:

Patch:
diff --git a/scalecodec/base.py b/scalecodec/base.py
index 63dbe9a..92b2a42 100644
--- a/scalecodec/base.py
+++ b/scalecodec/base.py
@@ -145,8 +145,16 @@ class RuntimeConfigurationObject:
 
         return decoder_class
 
-    def create_scale_object(self, type_string: str, data=None, **kwargs) -> 'ScaleType':
+    def create_scale_object(self, type_string: str, data: Optional['ScaleBytes'] = None, **kwargs) -> 'ScaleType':
         """
+        Creates a new `ScaleType` object with given type_string, for example 'u32', 'Bytes' or 'scale_info::2'
+        (scale_info:: prefixed types are defined in the `PortableRegistry` object of the runtime metadata.)
+
+        Parameters
+        ----------
+        type_string: string representation of a `ScaleType`
+        data: ScaleBytes data to decode
+        kwargs
 
         Returns
         -------
diff --git a/scalecodec/types.py b/scalecodec/types.py
index 687865d..e6a1cc7 100644
--- a/scalecodec/types.py
+++ b/scalecodec/types.py
@@ -14,6 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 import math
+import struct
 import warnings
 from datetime import datetime
 from hashlib import blake2b
@@ -423,6 +424,30 @@ class I256(ScaleType):
             raise ValueError('{} out of range for i256'.format(value))
 
 
+class F32(ScaleType):
+
+    def process(self):
+        return struct.unpack('f', self.get_next_bytes(4))[0]
+
+    def process_encode(self, value):
+        if type(value) is not float:
+            raise ValueError(f'{value} is not a float')
+
+        return ScaleBytes(struct.pack('f', value))
+
+
+class F64(ScaleType):
+
+    def process(self):
+        return struct.unpack('d', self.get_next_bytes(8))[0]
+
+    def process_encode(self, value):
+        if type(value) is not float:
+            raise ValueError(f'{value} is not a float')
+
+        return ScaleBytes(struct.pack('d', value))
+
+
 class H160(ScaleType):
 
     def process(self):


Test patch:
diff --git a/test/test_scale_types.py b/test/test_scale_types.py
index fa03243..6576ada 100644
--- a/test/test_scale_types.py
+++ b/test/test_scale_types.py
@@ -118,6 +118,16 @@ class TestScaleTypes(unittest.TestCase):
         obj.decode()
         self.assertEqual(obj.value, -1234)
 
+    def test_f64(self):
+        obj = RuntimeConfiguration().create_scale_object('f64', ScaleBytes("0x0000000000000080"))
+        obj.decode()
+        self.assertEqual(obj.value, -0.0)
+
+    def test_f32(self):
+        obj = RuntimeConfiguration().create_scale_object('f32', ScaleBytes("0x00000080"))
+        obj.decode()
+        self.assertEqual(obj.value, -0.0)
+
     def test_compact_bool_true(self):
         obj = RuntimeConfiguration().create_scale_object('bool', ScaleBytes("0x01"))
         obj.decode()
diff --git a/test/test_type_encoding.py b/test/test_type_encoding.py
index 0062822..a8cc9da 100644
--- a/test/test_type_encoding.py
+++ b/test/test_type_encoding.py
@@ -61,6 +61,22 @@ class TestScaleTypeEncoding(unittest.TestCase):
         obj = RuntimeConfiguration().create_scale_object('i16')
         self.assertRaises(ValueError, obj.encode, -32769)
 
+    def test_f64(self):
+        obj = RuntimeConfiguration().create_scale_object('f64')
+        obj.encode(-0.0)
+        self.assertEqual(str(obj.data), "0x0000000000000080")
+
+    def test_f64_invalid_input(self):
+        obj = RuntimeConfiguration().create_scale_object('f64')
+        with self.assertRaises(ValueError) as cm:
+            obj.encode(-0)
+            self.assertEqual('0 is not a float', str(cm.exception))
+
+    def test_f32(self):
+        obj = RuntimeConfiguration().create_scale_object('f32')
+        obj.encode(-0.0)
+        self.assertEqual(str(obj.data), "0x00000080")
+
     def test_compact_u32_1byte(self):
         obj = RuntimeConfiguration().create_scale_object('Compact<u32>', ScaleBytes("0x18"))
         obj.decode()