From 4c92c946b8f4129081c65f54fa01b8a8ed60d03d Mon Sep 17 00:00:00 2001 From: Rob Tillaart Date: Mon, 15 Jun 2026 18:54:10 +0200 Subject: [PATCH 1/6] fix sign() --- .github/workflows/arduino-lint.yml | 2 +- .github/workflows/arduino_test_runner.yml | 3 +-- .github/workflows/jsoncheck.yml | 2 +- CHANGELOG.md | 7 +++++++ LICENSE | 2 +- README.md | 18 ++++++++++++------ .../float16_sizeof_array.ino | 4 ++-- examples/float16_test0/float16_test0.ino | 16 +++++++++++++++- float16.cpp | 11 ++++++----- float16.h | 4 ++-- library.json | 2 +- library.properties | 2 +- 12 files changed, 50 insertions(+), 23 deletions(-) diff --git a/.github/workflows/arduino-lint.yml b/.github/workflows/arduino-lint.yml index 0ad60f4..aed264b 100644 --- a/.github/workflows/arduino-lint.yml +++ b/.github/workflows/arduino-lint.yml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: arduino/arduino-lint-action@v2 with: library-manager: update diff --git a/.github/workflows/arduino_test_runner.yml b/.github/workflows/arduino_test_runner.yml index 1897982..a2a5f07 100644 --- a/.github/workflows/arduino_test_runner.yml +++ b/.github/workflows/arduino_test_runner.yml @@ -6,9 +6,8 @@ jobs: runTest: runs-on: ubuntu-latest timeout-minutes: 20 - steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 diff --git a/.github/workflows/jsoncheck.yml b/.github/workflows/jsoncheck.yml index 8804e69..2c52dc7 100644 --- a/.github/workflows/jsoncheck.yml +++ b/.github/workflows/jsoncheck.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: json-syntax-check uses: limitusus/json-syntax-check@v2 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index ca6014d..1c94144 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.4.0] - 2026-06-15 +- fix **sign()** for -0 (0x8000) +- update GitHub actions +- minor edits + +---- + ## [0.3.1] - 2025-10-13 - update GitHub actions - update examples diff --git a/LICENSE b/LICENSE index 5446735..f85ccba 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2015-2025 Rob Tillaart +Copyright (c) 2015-2026 Rob Tillaart Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 8ec210e..6c79d26 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The primary usage of the float16 data type is to efficiently store and transport a floating point number. As it uses only 2 bytes where float and double have typical 4 and 8 bytes, gains can be made at the price of range and precision. -Note that float16 only has ~3 significant digits. +Note that a float16 only has ~3 significant digits. To print a float16, one need to convert it with toFloat(), toDouble() or toString(decimals). The latter allows concatenation and further conversion to an char array. @@ -32,10 +32,12 @@ The latter allows concatenation and further conversion to an char array. In pre 0.3.0 version the Printable interface was implemented, but it has been removed as it caused excessive memory usage when declaring arrays of float16. +Feedback as always is welcome. + ### ARM alternative half-precision --https://en.wikipedia.org/wiki/Half-precision_floating-point_format#ARM_alternative_half-precision +- https://en.wikipedia.org/wiki/Half-precision_floating-point_format#ARM_alternative_half-precision _ARM processors support (via a floating point control register bit) an "alternative half-precision" format, which does away with the @@ -56,7 +58,6 @@ the largest positive, the largest negative and the largest positive number. The -0 and 0 values will both exist. - Although they share a lot of code float16 and float16ext should not be mixed. In the future these libraries might merge / derive one from the other. @@ -68,6 +69,7 @@ it causes larger than expected arrays of float 16 (See #16). On ESP8266 every float16 object was 8 bytes and on AVR it was 5 bytes instead of the expected 2 bytes. To support printing the class added two new conversion functions: + ```cpp f16.toFloat(); f16.toString(decimals); @@ -75,6 +77,7 @@ f16.toString(decimals); Serial.println(f16.toFloat(), 4); Serial.println(f16.toString(4)); ``` + This keeps printing relative easy. The footprint of the library is now smaller and one can now create compact array's @@ -93,7 +96,7 @@ were also not implemented correctly. This is fixed too in 0.2.0. There is still an issue with 0 versus -0 (sign gets lost in conversion). -**This makes all pre-0.2.0 version obsolete.** +**This makes all pre-0.2.0 versions obsolete.** ## Specifications @@ -108,6 +111,7 @@ There is still an issue with 0 versus -0 (sign gets lost in conversion). | minimum | ±5.96046 E−8 | smallest number. | | ±1.0009765625 | 1 + 2^−10 = smallest number larger than 1. | maximum | ±65504 | +| range | | 12 orders of magnitude | | | ± = ALT 0177 @@ -147,6 +151,7 @@ Source: https://en.wikipedia.org/wiki/Half-precision_floating-point_format - https://github.com/RobTillaart/float16 - https://github.com/RobTillaart/float16ext - https://github.com/RobTillaart/fraction +- https://github.com/RobTillaart/printHelpers - scientific format a.o. - https://en.wikipedia.org/wiki/Half-precision_floating-point_format @@ -163,7 +168,7 @@ Source: https://en.wikipedia.org/wiki/Half-precision_floating-point_format - **float16(const float16 &f)** copy constructor. -### Conversion +### Conversion / printing - **double toDouble(void)** convert value to double or float (if the same e.g. UNO). - **float toFloat(void)** convert value to float. @@ -231,7 +236,8 @@ Math helpers. #### Should -- how to handle 0 == -0 (0x0000 == 0x8000) +- how to handle 0 == -0 (0x0000 == 0x8000) or (0x8000 == 0x0000) + - if ((_value & 0x7FFF) == 0x0000) return (f._value & 0x7FFF) == 0x0000; #### Could diff --git a/examples/float16_sizeof_array/float16_sizeof_array.ino b/examples/float16_sizeof_array/float16_sizeof_array.ino index 99f3f3e..19bb112 100644 --- a/examples/float16_sizeof_array/float16_sizeof_array.ino +++ b/examples/float16_sizeof_array/float16_sizeof_array.ino @@ -3,7 +3,7 @@ // AUTHOR: Rob Tillaart // PURPOSE: test float16 size // URL: https://github.com/RobTillaart/float16 -// See #12 +// See issue #12 #include "Arduino.h" #include "float16.h" @@ -35,7 +35,7 @@ void setup() Serial.println(sizeof(test32[0])); Serial.println(); - // set some values to make sure the compiler doesn't optimise out the arrays. + // set some values to make sure the compiler doesn't optimise out the arrays. test16[5] = 32; test32[4] = 32; diff --git a/examples/float16_test0/float16_test0.ino b/examples/float16_test0/float16_test0.ino index 374ae40..107c53d 100644 --- a/examples/float16_test0/float16_test0.ino +++ b/examples/float16_test0/float16_test0.ino @@ -42,6 +42,20 @@ void setup() Serial.println("Start "); + // // sign test + // float16 a(0); + // Serial.println(a.sign()); + // Serial.println(a.getBinary(), HEX); + // a.setBinary(0x8000); + // Serial.println(a.sign()); + // a.setBinary(0x0000); + // Serial.println(a.sign()); + // a.setBinary(0x8100); + // Serial.println(a.sign()); + // a.setBinary(0x0100); + // Serial.println(a.sign()); + // while (1); + Serial.println(); test_constructors(); @@ -133,7 +147,7 @@ void test_numbers() float16 c(1 / 2000.0); Serial.println(c.toDouble(), 9); - c = 1/3.0; + c = 1 / 3.0; Serial.println(c.toDouble(), 9); float16 d(1); diff --git a/float16.cpp b/float16.cpp index 1aaa0bb..9b830de 100644 --- a/float16.cpp +++ b/float16.cpp @@ -1,7 +1,7 @@ // // FILE: float16.cpp // AUTHOR: Rob Tillaart -// VERSION: 0.3.1 +// VERSION: 0.4.0 // DATE: 2015-03-10 // PURPOSE: library for Float16s for Arduino // URL: http://en.wikipedia.org/wiki/Half-precision_floating-point_format @@ -43,6 +43,7 @@ String float16::toString(unsigned int decimals) const // bool float16::operator == (const float16 &f) { + // if ((_value & 0x7FFF) == 0x0000) return (f._value & 0x7FFF) == 0x0000; return (_value == f._value); } @@ -151,9 +152,9 @@ float16& float16::operator /= (const float16 &f) // int float16::sign() { - if (_value & 0x8000) return -1; - if (_value & 0xFFFF) return 1; - return 0; + if ((_value & 0x7FFF) == 0x0000) return 0; + if (_value < 0x7C00) return 1; + return -1; } bool float16::isZero() @@ -225,7 +226,7 @@ float float16::f16tof32(uint16_t _value) const uint16_t float16::f32tof16(float f) const { - uint32_t t = *(uint32_t *) &f; + uint32_t t = *((uint32_t *) &f); // man bits = 10; but we keep 11 for rounding uint16_t man = (t & 0x007FFFFF) >> 12; int16_t exp = (t & 0x7F800000) >> 23; diff --git a/float16.h b/float16.h index 77b205d..43fb63d 100644 --- a/float16.h +++ b/float16.h @@ -2,7 +2,7 @@ // // FILE: float16.h // AUTHOR: Rob Tillaart -// VERSION: 0.3.1 +// VERSION: 0.4.0 // DATE: 2015-03-10 // PURPOSE: Arduino library to implement float16 data type. // half-precision floating point format, @@ -12,7 +12,7 @@ #include "Arduino.h" -#define FLOAT16_LIB_VERSION (F("0.3.1")) +#define FLOAT16_LIB_VERSION (F("0.4.0")) class float16 diff --git a/library.json b/library.json index 631261e..c1adff0 100644 --- a/library.json +++ b/library.json @@ -15,7 +15,7 @@ "type": "git", "url": "https://github.com/RobTillaart/float16.git" }, - "version": "0.3.1", + "version": "0.4.0", "license": "MIT", "frameworks": "*", "platforms": "*", diff --git a/library.properties b/library.properties index 88b84a9..612d72d 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=float16 -version=0.3.1 +version=0.4.0 author=Rob Tillaart maintainer=Rob Tillaart sentence=Arduino library to implement float16 data type. From a6e6fc2b0c3fa94c93c8353cf4f60a5a975d2ed8 Mon Sep 17 00:00:00 2001 From: Rob Tillaart Date: Mon, 15 Jun 2026 18:56:08 +0200 Subject: [PATCH 2/6] fix sign() --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 6c79d26..c999d5d 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,11 @@ Although they share a lot of code float16 and float16ext should not be mixed. In the future these libraries might merge / derive one from the other. +### Breaking change 0.4.0 + +Version 0.4.0 has fixed sign() which gave incorrect results for 0 and -0. + + ### Breaking change 0.3.0 Version 0.3.0 has a breaking change. The **Printable** interface is removed as From 865d13bba80a9f407ae0b4d043154dd76dce333d Mon Sep 17 00:00:00 2001 From: Rob Tillaart Date: Mon, 15 Jun 2026 19:33:48 +0200 Subject: [PATCH 3/6] add unit test sign() --- README.md | 50 +++++++++++++++++++++--------------------- float16.cpp | 5 ++++- test/unit_test_001.cpp | 21 ++++++++++++++++++ 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index c999d5d..ec40825 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ a floating point number. As it uses only 2 bytes where float and double have typ Note that a float16 only has ~3 significant digits. -To print a float16, one need to convert it with toFloat(), toDouble() or toString(decimals). +To print a float16, one need to convert it with toFloat(), toDouble() or toString(decimals). The latter allows concatenation and further conversion to an char array. In pre 0.3.0 version the Printable interface was implemented, but it has been removed @@ -39,10 +39,10 @@ Feedback as always is welcome. - https://en.wikipedia.org/wiki/Half-precision_floating-point_format#ARM_alternative_half-precision -_ARM processors support (via a floating point control register bit) -an "alternative half-precision" format, which does away with the -special case for an exponent value of 31 (111112).[10] It is almost -identical to the IEEE format, but there is no encoding for infinity or NaNs; +_ARM processors support (via a floating point control register bit) +an "alternative half-precision" format, which does away with the +special case for an exponent value of 31 (111112).[10] It is almost +identical to the IEEE format, but there is no encoding for infinity or NaNs; instead, an exponent of 31 encodes normalized numbers in the range 65536 to 131008._ Implemented in https://github.com/RobTillaart/float16ext class. @@ -50,7 +50,7 @@ Implemented in https://github.com/RobTillaart/float16ext class. ### Difference with float16 and float16ext -The float16ext library has an extended range as it supports values from +- 65504 +The float16ext library has an extended range as it supports values from +- 65504 to +- 131008. The float16ext does not support INF, -INF and NAN. These values are mapped upon @@ -69,7 +69,7 @@ Version 0.4.0 has fixed sign() which gave incorrect results for 0 and -0. ### Breaking change 0.3.0 -Version 0.3.0 has a breaking change. The **Printable** interface is removed as +Version 0.3.0 has a breaking change. The **Printable** interface is removed as it causes larger than expected arrays of float 16 (See #16). On ESP8266 every float16 object was 8 bytes and on AVR it was 5 bytes instead of the expected 2 bytes. @@ -93,15 +93,15 @@ of float16 elements using only 2 bytes per element. Version 0.2.0 has a breaking change as a conversion bug has been found. See for details in issue #10. -For some specific values the mantissa overflowed when the float 16 was +For some specific values the mantissa overflowed when the float 16 was assigned a value to. This overflow was not detected / corrected. -During the analysis of this bug it became clear that the sub-normal numbers +During the analysis of this bug it became clear that the sub-normal numbers were also not implemented correctly. This is fixed too in 0.2.0. There is still an issue with 0 versus -0 (sign gets lost in conversion). -**This makes all pre-0.2.0 versions obsolete.** +**This makes all pre-0.2.0 versions obsolete.** ## Specifications @@ -128,24 +128,24 @@ Source: https://en.wikipedia.org/wiki/Half-precision_floating-point_format ```cpp /* - SIGN EXP MANTISSA - 0 01111 0000000000 = 1 - 0 01111 0000000001 = 1 + 2−10 = 1.0009765625 (next smallest float after 1) - 1 10000 0000000000 = −2 + SIGN EXPONENT MANTISSA + 0 01111 0000000000 = 1 + 0 01111 0000000001 = 1 + 2−10 = 1.0009765625 (next smallest float after 1) + 1 10000 0000000000 = −2 - 0 11110 1111111111 = 65504 (max half precision) + 0 11110 1111111111 = 65504 (max half precision) - 0 00001 0000000000 = 2−14 ≈ 6.10352 × 10−5 (minimum positive normal) - 0 00000 1111111111 = 2−14 - 2−24 ≈ 6.09756 × 10−5 (maximum subnormal) - 0 00000 0000000001 = 2−24 ≈ 5.96046 × 10−8 (minimum positive subnormal) + 0 00001 0000000000 = 2−14 ≈ 6.10352 × 10−5 (minimum positive normal) + 0 00000 1111111111 = 2−14 - 2−24 ≈ 6.09756 × 10−5 (maximum subnormal) + 0 00000 0000000001 = 2−24 ≈ 5.96046 × 10−8 (minimum positive subnormal) - 0 00000 0000000000 = 0 - 1 00000 0000000000 = −0 + 0 00000 0000000000 = 0 + 1 00000 0000000000 = −0 - 0 11111 0000000000 = infinity - 1 11111 0000000000 = −infinity + 0 11111 0000000000 = infinity + 1 11111 0000000000 = −infinity - 0 01101 0101010101 = 0.333251953125 ≈ 1/3 + 0 01101 0101010101 = 0.333251953125 ≈ 1/3 */ ``` @@ -192,7 +192,7 @@ To serialize the internal format e.g. to disk, two helper functions are availabl ### Compare -The library implement the standard compare functions. +The library implement the standard compare functions. These are optimized, so it is fast to compare 2 float16 values. Note: comparison with a float or double always include a conversion. @@ -227,7 +227,7 @@ Negation operator. Math helpers. - **int sign()** returns 1 == positive, 0 == zero, -1 == negative. - **bool isZero()** returns true if zero. slightly faster than **sign()**. -- **bool isNaN()** returns true if value is not a number. +- **bool isNaN()** returns true if value is not a number. - **bool isInf()** returns true if value is ± infinite. - **bool isPosInf()** returns true if value is + infinite. - **bool isNegInf()** returns true if value is - infinite. diff --git a/float16.cpp b/float16.cpp index 9b830de..01512ae 100644 --- a/float16.cpp +++ b/float16.cpp @@ -152,8 +152,11 @@ float16& float16::operator /= (const float16 &f) // int float16::sign() { + // zero test matches 0x8000 too if ((_value & 0x7FFF) == 0x0000) return 0; - if (_value < 0x7C00) return 1; + // positive test including positive infinity + if (_value < 0x7FFF) return 1; + // remaining is negative return -1; } diff --git a/test/unit_test_001.cpp b/test/unit_test_001.cpp index 4484c89..4264ac6 100644 --- a/test/unit_test_001.cpp +++ b/test/unit_test_001.cpp @@ -95,6 +95,27 @@ unittest(test_sizeof) } +unittest(test_sign) +{ + float16 a(0); + assertEqual( 0, a.sign()); + + a.setBinary(0x0000); // +0 + assertEqual( 0, a.sign()); + a.setBinary(0x8000); // -0 + assertEqual( 0, a.sign()); + + a.setBinary(0x0100); + assertEqual( 1, a.sign()); + a.setBinary(0x0100); + assertEqual(-1, a.sign()); + + a.setBinary(0x7C00); // positive infinity + assertEqual( 1, a.sign()); + a.setBinary(0xFC00); // negative infinity + assertEqual(-1, a.sign()); +} + unittest(test_compare_equal) { float16 a(1); From d2ffc63f89ef24e42d1b9738eec1c6edb40c3f58 Mon Sep 17 00:00:00 2001 From: Rob Tillaart Date: Mon, 15 Jun 2026 19:52:37 +0200 Subject: [PATCH 4/6] fix --- README.md | 3 ++- float16.cpp | 2 +- test/unit_test_001.cpp | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ec40825..52b3f1b 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,8 @@ Source: https://en.wikipedia.org/wiki/Half-precision_floating-point_format 0 01111 0000000001 = 1 + 2−10 = 1.0009765625 (next smallest float after 1) 1 10000 0000000000 = −2 - 0 11110 1111111111 = 65504 (max half precision) + 0 11110 1111111111 = 65504 (max float16) + 0 11111 1111111111 = 131008 (max float16ext) 0 00001 0000000000 = 2−14 ≈ 6.10352 × 10−5 (minimum positive normal) 0 00000 1111111111 = 2−14 - 2−24 ≈ 6.09756 × 10−5 (maximum subnormal) diff --git a/float16.cpp b/float16.cpp index 01512ae..1bd8b9d 100644 --- a/float16.cpp +++ b/float16.cpp @@ -155,7 +155,7 @@ int float16::sign() // zero test matches 0x8000 too if ((_value & 0x7FFF) == 0x0000) return 0; // positive test including positive infinity - if (_value < 0x7FFF) return 1; + if (_value <= 0x7FFF) return 1; // remaining is negative return -1; } diff --git a/test/unit_test_001.cpp b/test/unit_test_001.cpp index 4264ac6..b497de3 100644 --- a/test/unit_test_001.cpp +++ b/test/unit_test_001.cpp @@ -116,6 +116,7 @@ unittest(test_sign) assertEqual(-1, a.sign()); } + unittest(test_compare_equal) { float16 a(1); From b51fbd89a865f0cae3916fb99704a10a289bdc2d Mon Sep 17 00:00:00 2001 From: Rob Tillaart Date: Mon, 15 Jun 2026 19:57:04 +0200 Subject: [PATCH 5/6] fix --- test/unit_test_001.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit_test_001.cpp b/test/unit_test_001.cpp index b497de3..743a02e 100644 --- a/test/unit_test_001.cpp +++ b/test/unit_test_001.cpp @@ -107,7 +107,7 @@ unittest(test_sign) a.setBinary(0x0100); assertEqual( 1, a.sign()); - a.setBinary(0x0100); + a.setBinary(0x8100); assertEqual(-1, a.sign()); a.setBinary(0x7C00); // positive infinity From d6aa0654949116525b0a785972fbc0ab0ad19832 Mon Sep 17 00:00:00 2001 From: Rob Tillaart Date: Mon, 15 Jun 2026 20:08:29 +0200 Subject: [PATCH 6/6] fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c94144..44f79b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [0.4.0] - 2026-06-15 - fix **sign()** for -0 (0x8000) +- update unit test - update GitHub actions - minor edits