From e81467378ebf8049e176387588e5ba4d622f718b Mon Sep 17 00:00:00 2001 From: Stephen Wray Date: Wed, 9 Sep 2026 13:39:25 +0000 Subject: [PATCH] Fix floating-point infinity literal generation SBE Tool accepts positive and negative Infinity for FLOAT and DOUBLE, but several target generators emit invalid source for those values. Generate width- and sign-correct infinity expressions for Java, C++, C, C#, Go, and Rust, and add parser, generator, and native regression coverage. --- build.gradle | 14 ++- csharp/sbe-tests/InfinityTests.cs | 62 ++++++++++++ gocode/Makefile | 4 +- .../src/infinity_test/infinity_test.go | 95 +++++++++++++++++++ .../struct/src/infinity_test/infinity_test.go | 74 +++++++++++++++ rust/Cargo.toml | 1 + rust/tests/infinity_test.rs | 49 ++++++++++ .../sbe/generation/c/CGenerator.java | 32 ++++++- .../sbe/generation/cpp/CppGenerator.java | 2 + .../sbe/generation/cpp/CppUtil.java | 30 +++++- .../sbe/generation/csharp/CSharpUtil.java | 12 +++ .../flyweight/GolangFlyweightGenerator.java | 11 +++ .../golang/struct/GolangGenerator.java | 30 +++++- .../sbe/generation/java/JavaUtil.java | 30 +++++- .../sbe/generation/rust/RustGenerator.java | 5 +- .../sbe/generation/rust/RustUtil.java | 11 ++- sbe-tool/src/test/c/CComplianceTest.c | 3 + sbe-tool/src/test/c/CMakeLists.txt | 5 +- sbe-tool/src/test/c/InfinityTest.cpp | 87 +++++++++++++++++ sbe-tool/src/test/cpp/CMakeLists.txt | 4 + sbe-tool/src/test/cpp/DtoTest.cpp | 33 +++++++ sbe-tool/src/test/cpp/InfinityTest.cpp | 88 +++++++++++++++++ .../co/real_logic/sbe/PrimitiveValueTest.java | 69 ++++++++++++++ .../sbe/generation/c/CGeneratorTest.java | 58 +++++++++++ .../sbe/generation/cpp/CppGeneratorTest.java | 25 +++++ .../sbe/generation/cpp/CppUtilTest.java | 50 ++++++++++ .../csharp/CSharpGeneratorTest.java | 25 +++++ .../sbe/generation/csharp/CSharpUtilTest.java | 50 ++++++++++ .../golang/GolangFlyweightGeneratorTest.java | 68 +++++++++++++ .../golang/GolangGeneratorTest.java | 32 +++++++ .../generation/java/JavaGeneratorTest.java | 29 ++++++ .../sbe/generation/java/JavaUtilTest.java | 50 ++++++++++ .../generation/rust/RustGeneratorTest.java | 67 +++++++++++++ .../sbe/generation/rust/RustUtilTest.java | 8 ++ .../co/real_logic/sbe/ir/EncodedIrTest.java | 6 ++ .../floating-point-infinity-schema.xml | 43 +++++++++ 36 files changed, 1246 insertions(+), 16 deletions(-) create mode 100644 csharp/sbe-tests/InfinityTests.cs create mode 100644 gocode/flyweight/src/infinity_test/infinity_test.go create mode 100644 gocode/struct/src/infinity_test/infinity_test.go create mode 100644 rust/tests/infinity_test.rs create mode 100644 sbe-tool/src/test/c/InfinityTest.cpp create mode 100644 sbe-tool/src/test/cpp/InfinityTest.cpp create mode 100644 sbe-tool/src/test/java/uk/co/real_logic/sbe/PrimitiveValueTest.java create mode 100644 sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/c/CGeneratorTest.java create mode 100644 sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/cpp/CppUtilTest.java create mode 100644 sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/csharp/CSharpUtilTest.java create mode 100644 sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/golang/GolangFlyweightGeneratorTest.java create mode 100644 sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/JavaUtilTest.java create mode 100644 sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/rust/RustGeneratorTest.java create mode 100644 sbe-tool/src/test/resources/floating-point-infinity-schema.xml diff --git a/build.gradle b/build.gradle index 159075f112..59709b53af 100644 --- a/build.gradle +++ b/build.gradle @@ -337,7 +337,8 @@ project(':sbe-tool') { 'sbe.java.precedence.checks.property.name': 'sbe.enable.test.precedence.checks') args = ['src/test/resources/json-printer-test-schema.xml', 'src/test/resources/composite-elements-schema.xml', - 'src/test/resources/field-order-check-schema.xml'] + 'src/test/resources/field-order-check-schema.xml', + 'src/test/resources/floating-point-infinity-schema.xml'] } tasks.register('generateTestDtos', JavaExec) { @@ -354,7 +355,9 @@ project(':sbe-tool') { 'sbe.java.precedence.checks.property.name': 'sbe.enable.test.precedence.checks', 'sbe.keyword.append.token': '$', 'sbe.java.generate.dtos': 'true') - args = ['src/test/resources/example-extension-schema.xml', 'src/test/resources/dto-test-schema.xml'] + args = ['src/test/resources/example-extension-schema.xml', + 'src/test/resources/dto-test-schema.xml', + 'src/test/resources/floating-point-infinity-schema.xml'] } jar { @@ -684,6 +687,7 @@ tasks.register('generateRustTestCodecs', JavaExec) { 'sbe-tool/src/test/resources/issue1066.xml', 'sbe-tool/src/test/resources/issue1116.xml', 'sbe-tool/src/test/resources/issue1118.xml', + 'sbe-tool/src/test/resources/floating-point-infinity-schema.xml', 'sbe-tool/src/test/resources/optional_enum_nullify.xml', 'sbe-tool/src/test/resources/basic-variable-length-schema.xml', 'sbe-tool/src/test/resources/example-bigendian-test-schema.xml', @@ -805,6 +809,7 @@ tasks.register('generateGolangCodecsWithXSD', JavaExec) { 'sbe-tool/src/test/resources/issue847.xml', 'sbe-tool/src/test/resources/issue848.xml', 'sbe-tool/src/test/resources/issue849.xml', + 'sbe-tool/src/test/resources/floating-point-infinity-schema.xml', 'sbe-tool/src/test/resources/since-deprecated-test-schema.xml', 'sbe-tool/src/test/resources/example-bigendian-test-schema.xml', 'gocode/resources/example-composite.xml', @@ -891,6 +896,7 @@ tasks.register('generateGolangFlyweightCodecsWithXSD', JavaExec) { 'sbe-tool/src/test/resources/issue847.xml', 'sbe-tool/src/test/resources/issue848.xml', 'sbe-tool/src/test/resources/issue849.xml', + 'sbe-tool/src/test/resources/floating-point-infinity-schema.xml', 'sbe-tool/src/test/resources/since-deprecated-test-schema.xml', 'sbe-tool/src/test/resources/example-bigendian-test-schema.xml', 'sbe-tool/src/test/resources/value-ref-with-lower-case-enum.xml', @@ -953,6 +959,7 @@ tasks.register('generateCSharpTestCodecs', JavaExec) { 'sbe-tool/src/test/resources/since-deprecated-test-schema.xml', 'sbe-tool/src/test/resources/example-bigendian-test-schema.xml', 'sbe-tool/src/test/resources/field-order-check-schema.xml', + 'sbe-tool/src/test/resources/floating-point-infinity-schema.xml', 'sbe-benchmarks/src/main/resources/fix-message-samples.xml'] } @@ -966,7 +973,8 @@ tasks.register('generateCSharpTestDtos', JavaExec) { 'sbe.xinclude.aware': 'true', 'sbe.validation.xsd': validationXsdPath, 'sbe.types.package.override': 'true') - args = ['sbe-samples/src/main/resources/example-extension-schema.xml'] + args = ['sbe-samples/src/main/resources/example-extension-schema.xml', + 'sbe-tool/src/test/resources/floating-point-infinity-schema.xml'] } tasks.register('generateCSharpExplicitPackageOverrideCodecs', JavaExec) { diff --git a/csharp/sbe-tests/InfinityTests.cs b/csharp/sbe-tests/InfinityTests.cs new file mode 100644 index 0000000000..9462049699 --- /dev/null +++ b/csharp/sbe-tests/InfinityTests.cs @@ -0,0 +1,62 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Org.SbeTool.Sbe.Dll; +using Infinity_test; + +namespace Org.SbeTool.Sbe.Tests +{ + [TestClass] + public class InfinityTests + { + [TestMethod] + public void ShouldRenderWidthAndSignCorrectInfinityConstants() + { + var buffer = new DirectBuffer(new byte[24]); + var values = new InfinityValues(); + values.WrapForEncode(buffer, 0); + + Assert.AreEqual(0x7f800000, BitConverter.SingleToInt32Bits(values.FloatPositiveInfinity)); + Assert.AreEqual(unchecked((int)0xff800000), BitConverter.SingleToInt32Bits(values.FloatNegativeInfinity)); + Assert.AreEqual(0x7ff0000000000000L, BitConverter.DoubleToInt64Bits(values.DoublePositiveInfinity)); + Assert.AreEqual(unchecked((long)0xfff0000000000000UL), BitConverter.DoubleToInt64Bits(values.DoubleNegativeInfinity)); + + Assert.AreEqual(0x7f800000, BitConverter.SingleToInt32Bits(InfinityValuesDto.FloatPositiveInfinity)); + Assert.AreEqual(unchecked((int)0xff800000), BitConverter.SingleToInt32Bits(InfinityValuesDto.FloatNegativeInfinity)); + Assert.AreEqual(0x7ff0000000000000L, BitConverter.DoubleToInt64Bits(InfinityValuesDto.DoublePositiveInfinity)); + Assert.AreEqual(unchecked((long)0xfff0000000000000UL), BitConverter.DoubleToInt64Bits(InfinityValuesDto.DoubleNegativeInfinity)); + Assert.AreEqual(0x7f800000, BitConverter.SingleToInt32Bits(FloatConstantsDto.Positive)); + Assert.AreEqual(unchecked((int)0xff800000), BitConverter.SingleToInt32Bits(FloatConstantsDto.Negative)); + + Assert.AreEqual(unchecked((int)0xff800000), BitConverter.SingleToInt32Bits(InfinityValues.FloatPositiveNullMinValue)); + Assert.AreEqual(1.5f, InfinityValues.FloatPositiveNullMaxValue); + Assert.AreEqual(-1.5f, InfinityValues.FloatNegativeNullMinValue); + Assert.AreEqual(0x7f800000, BitConverter.SingleToInt32Bits(InfinityValues.FloatNegativeNullMaxValue)); + Assert.AreEqual(unchecked((long)0xfff0000000000000UL), BitConverter.DoubleToInt64Bits(InfinityValues.DoublePositiveNullMinValue)); + Assert.AreEqual(1.5d, InfinityValues.DoublePositiveNullMaxValue); + Assert.AreEqual(-1.5d, InfinityValues.DoubleNegativeNullMinValue); + Assert.AreEqual(0x7ff0000000000000L, BitConverter.DoubleToInt64Bits(InfinityValues.DoubleNegativeNullMaxValue)); + + var constants = new FloatConstants(); + constants.WrapForEncode(buffer, 0); + Assert.AreEqual(0x7f800000, BitConverter.SingleToInt32Bits(constants.Positive)); + Assert.AreEqual(unchecked((int)0xff800000), BitConverter.SingleToInt32Bits(constants.Negative)); + } + + [TestMethod] + public void ShouldEncodeWidthAndSignCorrectOptionalNullValues() + { + var buffer = new DirectBuffer(new byte[24]); + var values = new InfinityValues(); + values.WrapForEncode(buffer, 0); + values.FloatPositiveNull = InfinityValues.FloatPositiveNullNullValue; + values.FloatNegativeNull = InfinityValues.FloatNegativeNullNullValue; + values.DoublePositiveNull = InfinityValues.DoublePositiveNullNullValue; + values.DoubleNegativeNull = InfinityValues.DoubleNegativeNullNullValue; + + Assert.AreEqual(0x7f800000, BitConverter.SingleToInt32Bits(values.FloatPositiveNull)); + Assert.AreEqual(unchecked((int)0xff800000), BitConverter.SingleToInt32Bits(values.FloatNegativeNull)); + Assert.AreEqual(0x7ff0000000000000L, BitConverter.DoubleToInt64Bits(values.DoublePositiveNull)); + Assert.AreEqual(unchecked((long)0xfff0000000000000UL), BitConverter.DoubleToInt64Bits(values.DoubleNegativeNull)); + } + } +} diff --git a/gocode/Makefile b/gocode/Makefile index a635c61f1d..1cb18cac94 100644 --- a/gocode/Makefile +++ b/gocode/Makefile @@ -72,7 +72,7 @@ test: $(DEP) go install \ ;done)) (export GOPATH=$(GOPATH)/struct && \ - (for t in baseline-bigendian mktdata group_with_data group_with_data_extension composite_elements composite since-deprecated simple issue435 issue472 issue483 issue488 issue560 issue847 issue848 issue849 issue505 test973; do \ + (for t in baseline-bigendian mktdata group_with_data group_with_data_extension composite_elements composite since-deprecated simple issue435 issue472 issue483 issue488 issue560 issue847 issue848 issue849 issue505 test973 infinity_test; do \ cd $(GOPATH)/struct/src/$$t && \ go build && \ go fmt && \ @@ -95,7 +95,7 @@ test: $(DEP) go install \ ;done)) (export GOPATH=$(GOPATH)/flyweight && \ - (for t in baseline-bigendian mktdata group_with_data group_with_data_extension composite_elements composite since-deprecated simple issue435 issue472 issue483 issue488 issue560 issue847 issue848 issue849; do \ + (for t in baseline-bigendian mktdata group_with_data group_with_data_extension composite_elements composite since-deprecated simple issue435 issue472 issue483 issue488 issue560 issue847 issue848 issue849 infinity_test; do \ cd $(GOPATH)/flyweight/src/$$t && \ go build && \ go fmt && \ diff --git a/gocode/flyweight/src/infinity_test/infinity_test.go b/gocode/flyweight/src/infinity_test/infinity_test.go new file mode 100644 index 0000000000..727902bc2f --- /dev/null +++ b/gocode/flyweight/src/infinity_test/infinity_test.go @@ -0,0 +1,95 @@ +package infinity_test + +import ( + "math" + "testing" +) + +func TestInfinityConstantsAndNullValues(t *testing.T) { + var values InfinityValues + + if got := math.Float32bits(values.FloatPositiveInfinity()); got != 0x7f800000 { + t.Fatalf("unexpected FLOAT positive infinity bits: %#x", got) + } + if got := math.Float32bits(values.FloatNegativeInfinity()); got != 0xff800000 { + t.Fatalf("unexpected FLOAT negative infinity bits: %#x", got) + } + if got := math.Float64bits(values.DoublePositiveInfinity()); got != 0x7ff0000000000000 { + t.Fatalf("unexpected DOUBLE positive infinity bits: %#x", got) + } + if got := math.Float64bits(values.DoubleNegativeInfinity()); got != 0xfff0000000000000 { + t.Fatalf("unexpected DOUBLE negative infinity bits: %#x", got) + } + + if got := math.Float32bits(values.FloatPositiveNullNullValue()); got != 0x7f800000 { + t.Fatalf("unexpected FLOAT positive null bits: %#x", got) + } + if got := math.Float32bits(values.FloatNegativeNullNullValue()); got != 0xff800000 { + t.Fatalf("unexpected FLOAT negative null bits: %#x", got) + } + if got := math.Float64bits(values.DoublePositiveNullNullValue()); got != 0x7ff0000000000000 { + t.Fatalf("unexpected DOUBLE positive null bits: %#x", got) + } + if got := math.Float64bits(values.DoubleNegativeNullNullValue()); got != 0xfff0000000000000 { + t.Fatalf("unexpected DOUBLE negative null bits: %#x", got) + } + + if got := math.Float32bits(values.FloatPositiveNullMinValue()); got != 0xff800000 { + t.Fatalf("unexpected FLOAT positive null minimum bits: %#x", got) + } + if got := values.FloatPositiveNullMaxValue(); got != 1.5 { + t.Fatalf("unexpected FLOAT positive null maximum: %v", got) + } + if got := values.FloatNegativeNullMinValue(); got != -1.5 { + t.Fatalf("unexpected FLOAT negative null minimum: %v", got) + } + if got := math.Float32bits(values.FloatNegativeNullMaxValue()); got != 0x7f800000 { + t.Fatalf("unexpected FLOAT negative null maximum bits: %#x", got) + } + if got := math.Float64bits(values.DoublePositiveNullMinValue()); got != 0xfff0000000000000 { + t.Fatalf("unexpected DOUBLE positive null minimum bits: %#x", got) + } + if got := values.DoublePositiveNullMaxValue(); got != 1.5 { + t.Fatalf("unexpected DOUBLE positive null maximum: %v", got) + } + if got := values.DoubleNegativeNullMinValue(); got != -1.5 { + t.Fatalf("unexpected DOUBLE negative null minimum: %v", got) + } + if got := math.Float64bits(values.DoubleNegativeNullMaxValue()); got != 0x7ff0000000000000 { + t.Fatalf("unexpected DOUBLE negative null maximum bits: %#x", got) + } +} + +func TestFloatConstants(t *testing.T) { + var values FloatConstants + + if got := math.Float32bits(values.Positive()); got != 0x7f800000 { + t.Fatalf("unexpected FLOAT positive constant bits: %#x", got) + } + if got := math.Float32bits(values.Negative()); got != 0xff800000 { + t.Fatalf("unexpected FLOAT negative constant bits: %#x", got) + } +} + +func TestInfinityValuesEncodeNulls(t *testing.T) { + var data [24]byte + var values InfinityValues + values.WrapForEncode(data[:], 0, uint64(len(data))) + values.SetFloatPositiveNull(values.FloatPositiveNullNullValue()) + values.SetFloatNegativeNull(values.FloatNegativeNullNullValue()) + values.SetDoublePositiveNull(values.DoublePositiveNullNullValue()) + values.SetDoubleNegativeNull(values.DoubleNegativeNullNullValue()) + + if got := math.Float32bits(values.FloatPositiveNull()); got != 0x7f800000 { + t.Fatalf("unexpected encoded FLOAT positive null bits: %#x", got) + } + if got := math.Float32bits(values.FloatNegativeNull()); got != 0xff800000 { + t.Fatalf("unexpected encoded FLOAT negative null bits: %#x", got) + } + if got := math.Float64bits(values.DoublePositiveNull()); got != 0x7ff0000000000000 { + t.Fatalf("unexpected encoded DOUBLE positive null bits: %#x", got) + } + if got := math.Float64bits(values.DoubleNegativeNull()); got != 0xfff0000000000000 { + t.Fatalf("unexpected encoded DOUBLE negative null bits: %#x", got) + } +} diff --git a/gocode/struct/src/infinity_test/infinity_test.go b/gocode/struct/src/infinity_test/infinity_test.go new file mode 100644 index 0000000000..5a8ba45621 --- /dev/null +++ b/gocode/struct/src/infinity_test/infinity_test.go @@ -0,0 +1,74 @@ +package infinity_test + +import ( + "math" + "testing" +) + +func TestInfinityConstantsAndNullValues(t *testing.T) { + var values InfinityValues + InfinityValuesInit(&values) + + if got := math.Float32bits(values.FloatPositiveInfinity); got != 0x7f800000 { + t.Fatalf("unexpected FLOAT positive infinity bits: %#x", got) + } + if got := math.Float32bits(values.FloatNegativeInfinity); got != 0xff800000 { + t.Fatalf("unexpected FLOAT negative infinity bits: %#x", got) + } + if got := math.Float64bits(values.DoublePositiveInfinity); got != 0x7ff0000000000000 { + t.Fatalf("unexpected DOUBLE positive infinity bits: %#x", got) + } + if got := math.Float64bits(values.DoubleNegativeInfinity); got != 0xfff0000000000000 { + t.Fatalf("unexpected DOUBLE negative infinity bits: %#x", got) + } + + if got := math.Float32bits(values.FloatPositiveNull); got != 0x7f800000 { + t.Fatalf("unexpected FLOAT positive null bits: %#x", got) + } + if got := math.Float32bits(values.FloatNegativeNull); got != 0xff800000 { + t.Fatalf("unexpected FLOAT negative null bits: %#x", got) + } + if got := math.Float64bits(values.DoublePositiveNull); got != 0x7ff0000000000000 { + t.Fatalf("unexpected DOUBLE positive null bits: %#x", got) + } + if got := math.Float64bits(values.DoubleNegativeNull); got != 0xfff0000000000000 { + t.Fatalf("unexpected DOUBLE negative null bits: %#x", got) + } + + if got := math.Float32bits(values.FloatPositiveNullMinValue()); got != 0xff800000 { + t.Fatalf("unexpected FLOAT positive null minimum bits: %#x", got) + } + if got := values.FloatPositiveNullMaxValue(); got != 1.5 { + t.Fatalf("unexpected FLOAT positive null maximum: %v", got) + } + if got := values.FloatNegativeNullMinValue(); got != -1.5 { + t.Fatalf("unexpected FLOAT negative null minimum: %v", got) + } + if got := math.Float32bits(values.FloatNegativeNullMaxValue()); got != 0x7f800000 { + t.Fatalf("unexpected FLOAT negative null maximum bits: %#x", got) + } + if got := math.Float64bits(values.DoublePositiveNullMinValue()); got != 0xfff0000000000000 { + t.Fatalf("unexpected DOUBLE positive null minimum bits: %#x", got) + } + if got := values.DoublePositiveNullMaxValue(); got != 1.5 { + t.Fatalf("unexpected DOUBLE positive null maximum: %v", got) + } + if got := values.DoubleNegativeNullMinValue(); got != -1.5 { + t.Fatalf("unexpected DOUBLE negative null minimum: %v", got) + } + if got := math.Float64bits(values.DoubleNegativeNullMaxValue()); got != 0x7ff0000000000000 { + t.Fatalf("unexpected DOUBLE negative null maximum bits: %#x", got) + } +} + +func TestFloatConstants(t *testing.T) { + var values FloatConstants + FloatConstantsInit(&values) + + if got := math.Float32bits(values.Positive); got != 0x7f800000 { + t.Fatalf("unexpected FLOAT positive constant bits: %#x", got) + } + if got := math.Float32bits(values.Negative); got != 0xff800000 { + t.Fatalf("unexpected FLOAT negative constant bits: %#x", got) + } +} diff --git a/rust/Cargo.toml b/rust/Cargo.toml index cccd25c1bd..4123874c68 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -21,6 +21,7 @@ issue_1057 = { path = "../generated/rust/issue1057" } issue_1066 = { path = "../generated/rust/issue1066" } issue_1116 = { path = "../generated/rust/issue1116" } issue_1118 = { path = "../generated/rust/issue1118" } +infinity_test = { path = "../generated/rust/infinity_test" } baseline_bigendian = { path = "../generated/rust/baseline_bigendian" } nested_composite_name = { path = "../generated/rust/nested_composite_name" } sbe_tests = { path = "../generated/rust/sbe_tests" } diff --git a/rust/tests/infinity_test.rs b/rust/tests/infinity_test.rs new file mode 100644 index 0000000000..a14f9634c1 --- /dev/null +++ b/rust/tests/infinity_test.rs @@ -0,0 +1,49 @@ +use infinity_test::{ + float_constants_codec::FloatConstantsDecoder, + infinity_values_codec::{self, InfinityValuesDecoder, InfinityValuesEncoder}, + ReadBuf, WriteBuf, +}; + +#[test] +fn should_render_width_and_sign_correct_constants() { + let constants = FloatConstantsDecoder::default(); + assert_eq!(constants.positive().to_bits(), 0x7f800000); + assert_eq!(constants.negative().to_bits(), 0xff800000); + + let values = InfinityValuesDecoder::default(); + assert_eq!(values.float_positive_infinity().to_bits(), 0x7f800000); + assert_eq!(values.float_negative_infinity().to_bits(), 0xff800000); + assert_eq!( + values.double_positive_infinity().to_bits(), + 0x7ff0000000000000 + ); + assert_eq!( + values.double_negative_infinity().to_bits(), + 0xfff0000000000000 + ); +} + +#[test] +fn should_encode_and_decode_width_and_sign_correct_optional_null_values() { + let mut buffer = vec![0u8; infinity_values_codec::SBE_BLOCK_LENGTH as usize]; + { + let mut encoder = + InfinityValuesEncoder::default().wrap(WriteBuf::new(buffer.as_mut_slice()), 0); + encoder + .float_positive_null_opt(None) + .float_negative_null_opt(None) + .double_positive_null_opt(None) + .double_negative_null_opt(None); + } + + let decoder = InfinityValuesDecoder::default().wrap( + ReadBuf::new(&buffer), + 0, + infinity_values_codec::SBE_BLOCK_LENGTH, + 0, + ); + assert!(decoder.float_positive_null().is_none()); + assert!(decoder.float_negative_null().is_none()); + assert!(decoder.double_positive_null().is_none()); + assert!(decoder.double_negative_null().is_none()); +} diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/c/CGenerator.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/c/CGenerator.java index b6dd9639f3..cc7e908f3d 100755 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/c/CGenerator.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/c/CGenerator.java @@ -1084,6 +1084,8 @@ private static CharSequence generateFileHeader(final String structName, final Li "#include \n" + "#define SBE_FLOAT_NAN NAN\n" + "#define SBE_DOUBLE_NAN NAN\n" + + "#define SBE_FLOAT_INFINITY ((float)INFINITY)\n" + + "#define SBE_DOUBLE_INFINITY ((double)INFINITY)\n" + "#include \n" + "#include \n" + "#include \n" + @@ -2496,7 +2498,20 @@ private static CharSequence generateLiteral(final PrimitiveType type, final Stri } case FLOAT: - literal = value.endsWith("NaN") ? "SBE_FLOAT_NAN" : value + "f"; + if (value.endsWith("NaN")) + { + literal = "SBE_FLOAT_NAN"; + } + else if (value.endsWith("Infinity")) + { + literal = value.startsWith("-") ? + "-SBE_FLOAT_INFINITY" : + "SBE_FLOAT_INFINITY"; + } + else + { + literal = value + "f"; + } break; case INT64: @@ -2518,7 +2533,20 @@ private static CharSequence generateLiteral(final PrimitiveType type, final Stri break; case DOUBLE: - literal = value.endsWith("NaN") ? "SBE_DOUBLE_NAN" : value; + if (value.endsWith("NaN")) + { + literal = "SBE_DOUBLE_NAN"; + } + else if (value.endsWith("Infinity")) + { + literal = value.startsWith("-") ? + "-SBE_DOUBLE_INFINITY" : + "SBE_DOUBLE_INFINITY"; + } + else + { + literal = value; + } break; } diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java index f6dbcced9a..7c7c76fb77 100755 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java @@ -1803,6 +1803,8 @@ private CharSequence generateFileHeader( "#define SBE_FLOAT_NAN std::numeric_limits::quiet_NaN()\n" + "#define SBE_DOUBLE_NAN std::numeric_limits::quiet_NaN()\n" + + "#define SBE_FLOAT_INFINITY std::numeric_limits::infinity()\n" + + "#define SBE_DOUBLE_INFINITY std::numeric_limits::infinity()\n" + "#define SBE_NULLVALUE_INT8 (std::numeric_limits::min)()\n" + "#define SBE_NULLVALUE_INT16 (std::numeric_limits::min)()\n" + "#define SBE_NULLVALUE_INT32 (std::numeric_limits::min)()\n" + diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppUtil.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppUtil.java index f076301379..b5c4b22237 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppUtil.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppUtil.java @@ -178,7 +178,20 @@ static CharSequence generateLiteral(final PrimitiveType type, final String value break; case FLOAT: - literal = value.endsWith("NaN") ? "SBE_FLOAT_NAN" : value + "f"; + if (value.endsWith("NaN")) + { + literal = "SBE_FLOAT_NAN"; + } + else if (value.endsWith("Infinity")) + { + literal = value.startsWith("-") ? + "-SBE_FLOAT_INFINITY" : + "SBE_FLOAT_INFINITY"; + } + else + { + literal = value + "f"; + } break; case INT64: @@ -198,7 +211,20 @@ static CharSequence generateLiteral(final PrimitiveType type, final String value break; case DOUBLE: - literal = value.endsWith("NaN") ? "SBE_DOUBLE_NAN" : value; + if (value.endsWith("NaN")) + { + literal = "SBE_DOUBLE_NAN"; + } + else if (value.endsWith("Infinity")) + { + literal = value.startsWith("-") ? + "-SBE_DOUBLE_INFINITY" : + "SBE_DOUBLE_INFINITY"; + } + else + { + literal = value; + } break; } diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpUtil.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpUtil.java index cfba4755e5..2c0cac479b 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpUtil.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpUtil.java @@ -59,6 +59,12 @@ static String generateLiteral(final PrimitiveType type, final String value) { literal = "float.NaN"; } + else if (value.endsWith("Infinity")) + { + literal = value.startsWith("-") ? + "float.NegativeInfinity" : + "float.PositiveInfinity"; + } else { literal = value + "f"; @@ -78,6 +84,12 @@ static String generateLiteral(final PrimitiveType type, final String value) { literal = "double.NaN"; } + else if (value.endsWith("Infinity")) + { + literal = value.startsWith("-") ? + "double.NegativeInfinity" : + "double.PositiveInfinity"; + } else { literal = value + "d"; diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/golang/flyweight/GolangFlyweightGenerator.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/golang/flyweight/GolangFlyweightGenerator.java index 036ffbf7ae..4fe84039cd 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/golang/flyweight/GolangFlyweightGenerator.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/golang/flyweight/GolangFlyweightGenerator.java @@ -2678,6 +2678,12 @@ private CharSequence generateLiteral(final PrimitiveType type, final String valu if (value.endsWith("NaN")) { literal = "math.NaN()"; + addInclude("math"); + } + else if (value.endsWith("Infinity")) + { + literal = "float32(math.Inf(" + (value.startsWith("-") ? "-1" : "1") + "))"; + addInclude("math"); } else { @@ -2708,6 +2714,11 @@ private CharSequence generateLiteral(final PrimitiveType type, final String valu literal = "math.NaN()"; addInclude("math"); } + else if (value.endsWith("Infinity")) + { + literal = "math.Inf(" + (value.startsWith("-") ? "-1" : "1") + ")"; + addInclude("math"); + } else { literal = value; diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/golang/struct/GolangGenerator.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/golang/struct/GolangGenerator.java index 32f61dec82..33faffdac8 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/golang/struct/GolangGenerator.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/golang/struct/GolangGenerator.java @@ -2390,11 +2390,37 @@ private CharSequence generateLiteral(final PrimitiveType type, final String valu break; case FLOAT: - literal = "float32(" + (value.endsWith("NaN") ? "math.NaN()" : value) + ")"; + if (value.endsWith("NaN")) + { + imports.peek().add("math"); + literal = "float32(math.NaN())"; + } + else if (value.endsWith("Infinity")) + { + imports.peek().add("math"); + literal = "float32(math.Inf(" + (value.startsWith("-") ? "-1" : "1") + "))"; + } + else + { + literal = "float32(" + value + ")"; + } break; case DOUBLE: - literal = value.endsWith("NaN") ? "math.NaN()" : value; + if (value.endsWith("NaN")) + { + imports.peek().add("math"); + literal = "math.NaN()"; + } + else if (value.endsWith("Infinity")) + { + imports.peek().add("math"); + literal = "math.Inf(" + (value.startsWith("-") ? "-1" : "1") + ")"; + } + else + { + literal = value; + } break; } diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/java/JavaUtil.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/java/JavaUtil.java index 680bc02c76..8c4fd77cb2 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/java/JavaUtil.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/java/JavaUtil.java @@ -313,7 +313,20 @@ public static String generateLiteral(final PrimitiveType type, final String valu break; case FLOAT: - literal = value.endsWith("NaN") ? "Float.NaN" : value + "f"; + if (value.endsWith("NaN")) + { + literal = "Float.NaN"; + } + else if (value.endsWith("Infinity")) + { + literal = value.startsWith("-") ? + "Float.NEGATIVE_INFINITY" : + "Float.POSITIVE_INFINITY"; + } + else + { + literal = value + "f"; + } break; case INT64: @@ -325,7 +338,20 @@ public static String generateLiteral(final PrimitiveType type, final String valu break; case DOUBLE: - literal = value.endsWith("NaN") ? "Double.NaN" : value + "d"; + if (value.endsWith("NaN")) + { + literal = "Double.NaN"; + } + else if (value.endsWith("Infinity")) + { + literal = value.startsWith("-") ? + "Double.NEGATIVE_INFINITY" : + "Double.POSITIVE_INFINITY"; + } + else + { + literal = value + "d"; + } break; } diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/rust/RustGenerator.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/rust/RustGenerator.java index e914870870..7037bc8983 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/rust/RustGenerator.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/rust/RustGenerator.java @@ -1096,7 +1096,10 @@ else if (JavaUtil.isUtf8Encoding(characterEncoding)) indent(sb, level, "pub fn %s(&self) -> %s {\n", formatFunctionName(name), rustPrimitiveType); - indent(sb, level + 1, "%s\n", rawConstValue); + final String constValue = (primitiveType == PrimitiveType.FLOAT || primitiveType == PrimitiveType.DOUBLE) && + rawConstValue.endsWith("Infinity") ? + generateRustLiteral(primitiveType, rawConstValue) : rawConstValue; + indent(sb, level + 1, "%s\n", constValue); indent(sb, level, "}\n\n"); } } diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/rust/RustUtil.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/rust/RustUtil.java index 836bab1a7b..d27ca6ab30 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/rust/RustUtil.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/rust/RustUtil.java @@ -96,7 +96,16 @@ static String generateRustLiteral(final PrimitiveType type, final String value) return "0x" + Long.toHexString(parseLong(value)) + '_' + typeName; case FLOAT: case DOUBLE: - return value.endsWith("NaN") ? typeName + "::NAN" : value + '_' + typeName; + if (value.endsWith("NaN")) + { + return typeName + "::NAN"; + } + else if (value.endsWith("Infinity")) + { + final String infinity = typeName + "::INFINITY"; + return value.startsWith("-") ? "-" + infinity : infinity; + } + return value + '_' + typeName; default: throw new IllegalArgumentException("Unsupported literal generation for type: " + type.primitiveName()); diff --git a/sbe-tool/src/test/c/CComplianceTest.c b/sbe-tool/src/test/c/CComplianceTest.c index 33f8070605..205a211675 100644 --- a/sbe-tool/src/test/c/CComplianceTest.c +++ b/sbe-tool/src/test/c/CComplianceTest.c @@ -16,6 +16,9 @@ #include "code_generation_test/car.h" #include "code_generation_test/messageHeader.h" +#include "infinity_test/floatConstants.h" +#include "infinity_test/infinityValues.h" + #include "group_with_data/testMessage1.h" #include "group_with_data/testMessage2.h" #include "group_with_data/testMessage3.h" diff --git a/sbe-tool/src/test/c/CMakeLists.txt b/sbe-tool/src/test/c/CMakeLists.txt index 646b9e43c9..ca959cde58 100644 --- a/sbe-tool/src/test/c/CMakeLists.txt +++ b/sbe-tool/src/test/c/CMakeLists.txt @@ -36,6 +36,7 @@ set(CODE_GENERATION_SCHEMA ${CODEC_SCHEMA_DIR}/code-generation-schema.xml) set(CODE_GENERATION_SCHEMA_WITH_VERSION ${CODEC_SCHEMA_DIR}/code-generation-schema-with-version.xml) set(GROUP_WITH_DATA_SCHEMA ${CODEC_SCHEMA_DIR}/group-with-data-schema.xml) set(ISSUE889_SCHEMA ${CODEC_SCHEMA_DIR}/issue889.xml) +set(FLOATING_POINT_INFINITY_SCHEMA ${CODEC_SCHEMA_DIR}/floating-point-infinity-schema.xml) set(GENERATED_CODECS ${C_CODEC_TARGET_DIR} @@ -43,7 +44,7 @@ set(GENERATED_CODECS add_custom_command( OUTPUT ${GENERATED_CODECS} - DEPENDS sbe-jar ${SBE_JAR} ${CODE_GENERATION_SCHEMA} ${CODE_GENERATION_SCHEMA_WITH_VERSION} ${GROUP_WITH_DATA_SCHEMA} ${ISSUE889_SCHEMA} + DEPENDS sbe-jar ${SBE_JAR} ${CODE_GENERATION_SCHEMA} ${CODE_GENERATION_SCHEMA_WITH_VERSION} ${GROUP_WITH_DATA_SCHEMA} ${ISSUE889_SCHEMA} ${FLOATING_POINT_INFINITY_SCHEMA} COMMAND ${Java_JAVA_EXECUTABLE} --add-opens java.base/jdk.internal.misc=ALL-UNNAMED -Dsbe.output.dir=${C_CODEC_TARGET_DIR} @@ -54,6 +55,7 @@ add_custom_command( ${CODE_GENERATION_SCHEMA_WITH_VERSION} ${GROUP_WITH_DATA_SCHEMA} ${ISSUE889_SCHEMA} + ${FLOATING_POINT_INFINITY_SCHEMA} ) add_custom_target(c_codecs DEPENDS ${GENERATED_CODECS}) @@ -63,6 +65,7 @@ sbe_test(BoundsCheckTest c_codecs) sbe_test(CodeGenTest c_codecs) sbe_test(GroupWithDataTest c_codecs) sbe_test(Issue889Test c_codecs) +sbe_test(InfinityTest c_codecs) # Compile a dummy C source to test C compliance of generated headers. add_executable(CComplianceTest CComplianceTest.c) diff --git a/sbe-tool/src/test/c/InfinityTest.cpp b/sbe-tool/src/test/c/InfinityTest.cpp new file mode 100644 index 0000000000..7b49e39864 --- /dev/null +++ b/sbe-tool/src/test/c/InfinityTest.cpp @@ -0,0 +1,87 @@ +/* + * Copyright 2013-2025 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +#include "infinity_test/floatConstants.h" +#include "infinity_test/infinityValues.h" + +#define IT(name) infinity_test_##name + +namespace +{ +std::uint32_t floatBits(const float value) +{ + std::uint32_t bits; + std::memcpy(&bits, &value, sizeof(bits)); + return bits; +} + +std::uint64_t doubleBits(const double value) +{ + std::uint64_t bits; + std::memcpy(&bits, &value, sizeof(bits)); + return bits; +} +} + +TEST(InfinityTest, shouldGenerateWidthAndSignCorrectConstants) +{ + EXPECT_EQ(0x7f800000U, floatBits(IT(infinityValues_floatPositiveInfinity)())); + EXPECT_EQ(0xff800000U, floatBits(IT(infinityValues_floatNegativeInfinity)())); + EXPECT_EQ(0x7ff0000000000000ULL, doubleBits(IT(infinityValues_doublePositiveInfinity)())); + EXPECT_EQ(0xfff0000000000000ULL, doubleBits(IT(infinityValues_doubleNegativeInfinity)())); + + EXPECT_EQ(0x7f800000U, floatBits(IT(floatConstants_positive)())); + EXPECT_EQ(0xff800000U, floatBits(IT(floatConstants_negative)())); +} + +TEST(InfinityTest, shouldGenerateWidthAndSignCorrectBounds) +{ + EXPECT_EQ(0xff800000U, floatBits(IT(infinityValues_floatPositiveNull_min_value)())); + EXPECT_FLOAT_EQ(1.5f, IT(infinityValues_floatPositiveNull_max_value)()); + EXPECT_FLOAT_EQ(-1.5f, IT(infinityValues_floatNegativeNull_min_value)()); + EXPECT_EQ(0x7f800000U, floatBits(IT(infinityValues_floatNegativeNull_max_value)())); + EXPECT_EQ(0xfff0000000000000ULL, doubleBits(IT(infinityValues_doublePositiveNull_min_value)())); + EXPECT_DOUBLE_EQ(1.5, IT(infinityValues_doublePositiveNull_max_value)()); + EXPECT_DOUBLE_EQ(-1.5, IT(infinityValues_doubleNegativeNull_min_value)()); + EXPECT_EQ(0x7ff0000000000000ULL, doubleBits(IT(infinityValues_doubleNegativeNull_max_value)())); +} + +TEST(InfinityTest, shouldEncodeWidthAndSignCorrectOptionalNullValues) +{ + char buffer[24] = {}; + IT(infinityValues) codec; + IT(infinityValues_wrap_for_encode)(&codec, buffer, 0, sizeof(buffer)); + IT(infinityValues_set_floatPositiveNull)(&codec, IT(infinityValues_floatPositiveNull_null_value)()); + IT(infinityValues_set_floatNegativeNull)(&codec, IT(infinityValues_floatNegativeNull_null_value)()); + IT(infinityValues_set_doublePositiveNull)(&codec, IT(infinityValues_doublePositiveNull_null_value)()); + IT(infinityValues_set_doubleNegativeNull)(&codec, IT(infinityValues_doubleNegativeNull_null_value)()); + + std::uint32_t floatValue; + std::uint64_t doubleValue; + std::memcpy(&floatValue, buffer, sizeof(floatValue)); + EXPECT_EQ(0x7f800000U, floatValue); + std::memcpy(&floatValue, buffer + sizeof(floatValue), sizeof(floatValue)); + EXPECT_EQ(0xff800000U, floatValue); + std::memcpy(&doubleValue, buffer + 8, sizeof(doubleValue)); + EXPECT_EQ(0x7ff0000000000000ULL, doubleValue); + std::memcpy(&doubleValue, buffer + 16, sizeof(doubleValue)); + EXPECT_EQ(0xfff0000000000000ULL, doubleValue); +} diff --git a/sbe-tool/src/test/cpp/CMakeLists.txt b/sbe-tool/src/test/cpp/CMakeLists.txt index 0137b6de35..552a3a2ce3 100644 --- a/sbe-tool/src/test/cpp/CMakeLists.txt +++ b/sbe-tool/src/test/cpp/CMakeLists.txt @@ -40,6 +40,7 @@ set(GROUP_WITH_DATA_SCHEMA ${CODEC_SCHEMA_DIR}/group-with-data-schema.xml) set(DTO_SCHEMA ${CODEC_SCHEMA_DIR}/dto-test-schema.xml) set(ISSUE835_SCHEMA ${CODEC_SCHEMA_DIR}/issue835.xml) set(ISSUE889_SCHEMA ${CODEC_SCHEMA_DIR}/issue889.xml) +set(FLOATING_POINT_INFINITY_SCHEMA ${CODEC_SCHEMA_DIR}/floating-point-infinity-schema.xml) set(ACCESS_ORDER_SCHEMA ${CODEC_SCHEMA_DIR}/field-order-check-schema.xml) set(VERSIONED_MESSAGE_V1_SCHEMA ${CODEC_SCHEMA_DIR}/versioned-message-v1.xml) set(VERSIONED_MESSAGE_V2_SCHEMA ${CODEC_SCHEMA_DIR}/versioned-message-v2.xml) @@ -58,6 +59,7 @@ add_custom_command( ${DTO_SCHEMA} ${ISSUE835_SCHEMA} ${ISSUE889_SCHEMA} + ${FLOATING_POINT_INFINITY_SCHEMA} ${ACCESS_ORDER_SCHEMA} ${VERSIONED_MESSAGE_V1_SCHEMA} ${VERSIONED_MESSAGE_V2_SCHEMA} @@ -81,6 +83,7 @@ add_custom_command( ${DTO_SCHEMA} ${ISSUE835_SCHEMA} ${ISSUE889_SCHEMA} + ${FLOATING_POINT_INFINITY_SCHEMA} ${ACCESS_ORDER_SCHEMA} ${VERSIONED_MESSAGE_V1_SCHEMA} ${VERSIONED_MESSAGE_V2_SCHEMA} @@ -99,6 +102,7 @@ sbe_test(Rc3OtfFullIrTest codecs) sbe_test(CompositeElementsTest codecs) sbe_test(Issue835Test codecs) sbe_test(Issue889Test codecs) +sbe_test(InfinityTest codecs) sbe_test(FieldAccessOrderCheckTest codecs) target_compile_definitions(FieldAccessOrderCheckTest PRIVATE SBE_ENABLE_PRECEDENCE_CHECKS_IN_TESTS) sbe_test(VersionedMessageTest codecs) diff --git a/sbe-tool/src/test/cpp/DtoTest.cpp b/sbe-tool/src/test/cpp/DtoTest.cpp index c7eb7ce88c..6d556f3381 100644 --- a/sbe-tool/src/test/cpp/DtoTest.cpp +++ b/sbe-tool/src/test/cpp/DtoTest.cpp @@ -18,9 +18,13 @@ #error DTO code requires at least C++17. #endif +#include + #include #include "dto_test/ExtendedCar.h" #include "dto_test/ExtendedCarDto.h" +#include "infinity_test/FloatConstantsDto.h" +#include "infinity_test/InfinityValuesDto.h" using namespace dto_test; @@ -169,6 +173,35 @@ class DtoTest : public testing::Test } }; +namespace +{ +std::uint32_t floatBits(const float value) +{ + std::uint32_t bits; + std::memcpy(&bits, &value, sizeof(bits)); + return bits; +} + +std::uint64_t doubleBits(const double value) +{ + std::uint64_t bits; + std::memcpy(&bits, &value, sizeof(bits)); + return bits; +} +} + +TEST_F(DtoTest, shouldGenerateInfinityConstants) +{ + EXPECT_EQ(0x7f800000U, floatBits(infinity_test::InfinityValuesDto::floatPositiveInfinity())); + EXPECT_EQ(0xff800000U, floatBits(infinity_test::InfinityValuesDto::floatNegativeInfinity())); + EXPECT_EQ(0x7ff0000000000000ULL, + doubleBits(infinity_test::InfinityValuesDto::doublePositiveInfinity())); + EXPECT_EQ(0xfff0000000000000ULL, + doubleBits(infinity_test::InfinityValuesDto::doubleNegativeInfinity())); + EXPECT_EQ(0x7f800000U, floatBits(infinity_test::FloatConstantsDto::positive())); + EXPECT_EQ(0xff800000U, floatBits(infinity_test::FloatConstantsDto::negative())); +} + TEST_F(DtoTest, shouldRoundTripCar1) { char input[BUFFER_LEN]; diff --git a/sbe-tool/src/test/cpp/InfinityTest.cpp b/sbe-tool/src/test/cpp/InfinityTest.cpp new file mode 100644 index 0000000000..279aba86d4 --- /dev/null +++ b/sbe-tool/src/test/cpp/InfinityTest.cpp @@ -0,0 +1,88 @@ +/* + * Copyright 2013-2025 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include + +#include "infinity_test/FloatConstants.h" +#include "infinity_test/InfinityValues.h" + +using namespace infinity_test; + +namespace +{ +std::uint32_t floatBits(const float value) +{ + std::uint32_t bits; + std::memcpy(&bits, &value, sizeof(bits)); + return bits; +} + +std::uint64_t doubleBits(const double value) +{ + std::uint64_t bits; + std::memcpy(&bits, &value, sizeof(bits)); + return bits; +} +} + +TEST(InfinityTest, shouldGenerateWidthAndSignCorrectConstants) +{ + EXPECT_EQ(0x7f800000U, floatBits(InfinityValues::floatPositiveInfinity())); + EXPECT_EQ(0xff800000U, floatBits(InfinityValues::floatNegativeInfinity())); + EXPECT_EQ(0x7ff0000000000000ULL, doubleBits(InfinityValues::doublePositiveInfinity())); + EXPECT_EQ(0xfff0000000000000ULL, doubleBits(InfinityValues::doubleNegativeInfinity())); + + EXPECT_EQ(0x7f800000U, floatBits(FloatConstants::positive())); + EXPECT_EQ(0xff800000U, floatBits(FloatConstants::negative())); +} + +TEST(InfinityTest, shouldGenerateWidthAndSignCorrectBounds) +{ + EXPECT_EQ(0xff800000U, floatBits(InfinityValues::floatPositiveNullMinValue())); + EXPECT_FLOAT_EQ(1.5f, InfinityValues::floatPositiveNullMaxValue()); + EXPECT_FLOAT_EQ(-1.5f, InfinityValues::floatNegativeNullMinValue()); + EXPECT_EQ(0x7f800000U, floatBits(InfinityValues::floatNegativeNullMaxValue())); + EXPECT_EQ(0xfff0000000000000ULL, doubleBits(InfinityValues::doublePositiveNullMinValue())); + EXPECT_DOUBLE_EQ(1.5, InfinityValues::doublePositiveNullMaxValue()); + EXPECT_DOUBLE_EQ(-1.5, InfinityValues::doubleNegativeNullMinValue()); + EXPECT_EQ(0x7ff0000000000000ULL, doubleBits(InfinityValues::doubleNegativeNullMaxValue())); +} + +TEST(InfinityTest, shouldEncodeWidthAndSignCorrectOptionalNullValues) +{ + std::vector buffer(InfinityValues::sbeBlockLength()); + InfinityValues codec; + codec.wrapForEncode(buffer.data(), 0, buffer.size()) + .floatPositiveNull(InfinityValues::floatPositiveNullNullValue()) + .floatNegativeNull(InfinityValues::floatNegativeNullNullValue()) + .doublePositiveNull(InfinityValues::doublePositiveNullNullValue()) + .doubleNegativeNull(InfinityValues::doubleNegativeNullNullValue()); + + std::uint32_t floatValue; + std::uint64_t doubleValue; + std::memcpy(&floatValue, buffer.data(), sizeof(floatValue)); + EXPECT_EQ(0x7f800000U, floatValue); + std::memcpy(&floatValue, buffer.data() + sizeof(floatValue), sizeof(floatValue)); + EXPECT_EQ(0xff800000U, floatValue); + std::memcpy(&doubleValue, buffer.data() + 8, sizeof(doubleValue)); + EXPECT_EQ(0x7ff0000000000000ULL, doubleValue); + std::memcpy(&doubleValue, buffer.data() + 16, sizeof(doubleValue)); + EXPECT_EQ(0xfff0000000000000ULL, doubleValue); +} diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/PrimitiveValueTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/PrimitiveValueTest.java new file mode 100644 index 0000000000..fe9a29648a --- /dev/null +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/PrimitiveValueTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013-2025 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package uk.co.real_logic.sbe; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static uk.co.real_logic.sbe.PrimitiveValue.Representation.DOUBLE; + +class PrimitiveValueTest +{ + @ParameterizedTest + @CsvSource({ + "FLOAT,NaN,4,NaN", + "DOUBLE,NaN,8,NaN", + "FLOAT,Infinity,4,Infinity", + "FLOAT,+Infinity,4,Infinity", + "FLOAT,-Infinity,4,-Infinity", + "DOUBLE,Infinity,8,Infinity", + "DOUBLE,+Infinity,8,Infinity", + "DOUBLE,-Infinity,8,-Infinity" + }) + void shouldParseFloatingPointSpecialValues( + final PrimitiveType type, + final String input, + final int size, + final String expectedString) + { + final PrimitiveValue value = PrimitiveValue.parse(input, type); + + assertEquals(DOUBLE, value.representation()); + assertEquals(size, value.size()); + assertEquals(expectedString, value.toString()); + + if (expectedString.equals("NaN")) + { + assertTrue(Double.isNaN(value.doubleValue())); + } + else + { + assertTrue(Double.isInfinite(value.doubleValue())); + assertEquals(expectedString.startsWith("-"), + Double.doubleToRawLongBits(value.doubleValue()) < 0); + } + } + + @Test + void shouldPreserveFiniteFloatingPointValues() + { + assertEquals("1.5", PrimitiveValue.parse("1.5", PrimitiveType.FLOAT).toString()); + assertEquals("1.5", PrimitiveValue.parse("1.5", PrimitiveType.DOUBLE).toString()); + } +} diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/c/CGeneratorTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/c/CGeneratorTest.java new file mode 100644 index 0000000000..da37923b50 --- /dev/null +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/c/CGeneratorTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2013-2025 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package uk.co.real_logic.sbe.generation.c; + +import org.agrona.generation.StringWriterOutputManager; +import org.junit.jupiter.api.Test; +import uk.co.real_logic.sbe.Tests; +import uk.co.real_logic.sbe.ir.Ir; +import uk.co.real_logic.sbe.xml.IrGenerator; +import uk.co.real_logic.sbe.xml.MessageSchema; +import uk.co.real_logic.sbe.xml.ParserOptions; + +import java.io.InputStream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static uk.co.real_logic.sbe.xml.XmlSchemaParser.parse; + +class CGeneratorTest +{ + @Test + void shouldGenerateFloatingPointInfinityLiterals() throws Exception + { + try (InputStream in = Tests.getLocalResource("floating-point-infinity-schema.xml")) + { + final ParserOptions options = ParserOptions.builder().stopOnError(true).build(); + final MessageSchema schema = parse(in, options); + final Ir ir = new IrGenerator().generate(schema); + final StringWriterOutputManager outputManager = new StringWriterOutputManager(); + outputManager.setPackageName(ir.applicableNamespace()); + + new CGenerator(ir, outputManager).generate(); + + final String source = outputManager.getSources().values().stream() + .map(CharSequence::toString) + .collect(java.util.stream.Collectors.joining("\n")); + assertThat(source, containsString("#define SBE_FLOAT_INFINITY ((float)INFINITY)")); + assertThat(source, containsString("#define SBE_DOUBLE_INFINITY ((double)INFINITY)")); + assertThat(source, containsString("SBE_FLOAT_INFINITY")); + assertThat(source, containsString("-SBE_FLOAT_INFINITY")); + assertThat(source, containsString("SBE_DOUBLE_INFINITY")); + assertThat(source, containsString("-SBE_DOUBLE_INFINITY")); + } + } +} diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/cpp/CppGeneratorTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/cpp/CppGeneratorTest.java index 8d0b3995fa..0da91cae7b 100644 --- a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/cpp/CppGeneratorTest.java +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/cpp/CppGeneratorTest.java @@ -55,6 +55,31 @@ void shouldUseGeneratedLiteralForConstantOneWhenGeneratingBitsetCode() throws Ex } } + @Test + void shouldGenerateFloatingPointInfinityLiterals() throws Exception + { + try (InputStream in = Tests.getLocalResource("floating-point-infinity-schema.xml")) + { + final ParserOptions options = ParserOptions.builder().stopOnError(true).build(); + final MessageSchema schema = parse(in, options); + final Ir ir = new IrGenerator().generate(schema); + final StringWriterOutputManager outputManager = new StringWriterOutputManager(); + outputManager.setPackageName(ir.applicableNamespace()); + + new CppGenerator(ir, false, outputManager).generate(); + + final String source = outputManager.getSources().values().stream() + .map(CharSequence::toString) + .collect(java.util.stream.Collectors.joining("\n")); + assertThat(source, containsString( + "#define SBE_FLOAT_INFINITY std::numeric_limits::infinity()")); + assertThat(source, containsString( + "#define SBE_DOUBLE_INFINITY std::numeric_limits::infinity()")); + assertThat(source, containsString("SBE_FLOAT_INFINITY")); + assertThat(source, containsString("-SBE_DOUBLE_INFINITY")); + } + } + @Test void shouldUseConstexprWhenInitializingSemanticVersion() throws Exception { diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/cpp/CppUtilTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/cpp/CppUtilTest.java new file mode 100644 index 0000000000..bd747742bb --- /dev/null +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/cpp/CppUtilTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013-2025 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package uk.co.real_logic.sbe.generation.cpp; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static uk.co.real_logic.sbe.PrimitiveType.DOUBLE; +import static uk.co.real_logic.sbe.PrimitiveType.FLOAT; +import static uk.co.real_logic.sbe.PrimitiveType.INT32; +import static uk.co.real_logic.sbe.generation.cpp.CppUtil.generateLiteral; + +class CppUtilTest +{ + @Test + void shouldGenerateFloatingPointInfinityLiterals() + { + assertEquals("SBE_FLOAT_INFINITY", generateLiteral(FLOAT, "Infinity")); + assertEquals("-SBE_FLOAT_INFINITY", generateLiteral(FLOAT, "-Infinity")); + assertEquals("SBE_DOUBLE_INFINITY", generateLiteral(DOUBLE, "Infinity")); + assertEquals("-SBE_DOUBLE_INFINITY", generateLiteral(DOUBLE, "-Infinity")); + } + + @Test + void shouldPreserveExistingFloatingPointAndIntegerLiterals() + { + assertEquals("SBE_FLOAT_NAN", generateLiteral(FLOAT, "NaN")); + assertEquals("SBE_DOUBLE_NAN", generateLiteral(DOUBLE, "NaN")); + assertEquals("1.5f", generateLiteral(FLOAT, "1.5")); + assertEquals("-1.5f", generateLiteral(FLOAT, "-1.5")); + assertEquals("-0.0f", generateLiteral(FLOAT, "-0.0")); + assertEquals("1.5", generateLiteral(DOUBLE, "1.5")); + assertEquals("-1.5", generateLiteral(DOUBLE, "-1.5")); + assertEquals("-0.0", generateLiteral(DOUBLE, "-0.0")); + assertEquals("INT32_C(1)", generateLiteral(INT32, "1")); + } +} diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/csharp/CSharpGeneratorTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/csharp/CSharpGeneratorTest.java index d19639b2fd..a9ac66ea5f 100644 --- a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/csharp/CSharpGeneratorTest.java +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/csharp/CSharpGeneratorTest.java @@ -105,4 +105,29 @@ void shouldGenerateValidCode() throws Exception """)); } } + + @Test + void shouldGenerateFloatingPointInfinityLiterals() throws Exception + { + try (InputStream in = Tests.getLocalResource("floating-point-infinity-schema.xml")) + { + final ParserOptions options = ParserOptions.builder().stopOnError(true).build(); + final MessageSchema schema = parse(in, options); + final Ir ir = new IrGenerator().generate(schema); + final StringWriterOutputManager outputManager = new StringWriterOutputManager(); + outputManager.setPackageName(ir.applicableNamespace()); + + new CSharpGenerator( + ir, + PrecedenceChecks.newInstance(new PrecedenceChecks.Context()), + true, + outputManager).generate(); + + final String source = outputManager.getSources().values().stream() + .map(CharSequence::toString) + .collect(java.util.stream.Collectors.joining("\n")); + assertThat(source, containsString("float.PositiveInfinity")); + assertThat(source, containsString("double.NegativeInfinity")); + } + } } diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/csharp/CSharpUtilTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/csharp/CSharpUtilTest.java new file mode 100644 index 0000000000..52c01feebc --- /dev/null +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/csharp/CSharpUtilTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013-2025 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package uk.co.real_logic.sbe.generation.csharp; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static uk.co.real_logic.sbe.PrimitiveType.DOUBLE; +import static uk.co.real_logic.sbe.PrimitiveType.FLOAT; +import static uk.co.real_logic.sbe.PrimitiveType.INT32; +import static uk.co.real_logic.sbe.generation.csharp.CSharpUtil.generateLiteral; + +class CSharpUtilTest +{ + @Test + void shouldGenerateFloatingPointInfinityLiterals() + { + assertEquals("float.PositiveInfinity", generateLiteral(FLOAT, "Infinity")); + assertEquals("float.NegativeInfinity", generateLiteral(FLOAT, "-Infinity")); + assertEquals("double.PositiveInfinity", generateLiteral(DOUBLE, "Infinity")); + assertEquals("double.NegativeInfinity", generateLiteral(DOUBLE, "-Infinity")); + } + + @Test + void shouldPreserveExistingFloatingPointAndIntegerLiterals() + { + assertEquals("float.NaN", generateLiteral(FLOAT, "NaN")); + assertEquals("double.NaN", generateLiteral(DOUBLE, "NaN")); + assertEquals("1.5f", generateLiteral(FLOAT, "1.5")); + assertEquals("-1.5f", generateLiteral(FLOAT, "-1.5")); + assertEquals("-0.0f", generateLiteral(FLOAT, "-0.0")); + assertEquals("1.5d", generateLiteral(DOUBLE, "1.5")); + assertEquals("-1.5d", generateLiteral(DOUBLE, "-1.5")); + assertEquals("-0.0d", generateLiteral(DOUBLE, "-0.0")); + assertEquals("1", generateLiteral(INT32, "1")); + } +} diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/golang/GolangFlyweightGeneratorTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/golang/GolangFlyweightGeneratorTest.java new file mode 100644 index 0000000000..005d07c8f1 --- /dev/null +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/golang/GolangFlyweightGeneratorTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2013-2025 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package uk.co.real_logic.sbe.generation.golang; + +import org.agrona.generation.StringWriterOutputManager; +import org.junit.jupiter.api.Test; +import uk.co.real_logic.sbe.Tests; +import uk.co.real_logic.sbe.generation.golang.flyweight.GolangFlyweightGenerator; +import uk.co.real_logic.sbe.ir.Ir; +import uk.co.real_logic.sbe.xml.IrGenerator; +import uk.co.real_logic.sbe.xml.MessageSchema; +import uk.co.real_logic.sbe.xml.ParserOptions; + +import java.io.InputStream; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static uk.co.real_logic.sbe.xml.XmlSchemaParser.parse; + +class GolangFlyweightGeneratorTest +{ + @Test + void shouldGenerateFloatingPointInfinityLiterals() throws Exception + { + try (InputStream in = Tests.getLocalResource("floating-point-infinity-schema.xml")) + { + final ParserOptions options = ParserOptions.builder().stopOnError(true).build(); + final MessageSchema schema = parse(in, options); + final Ir ir = new IrGenerator().generate(schema); + final StringWriterOutputManager outputManager = new StringWriterOutputManager(); + outputManager.setPackageName(ir.applicableNamespace()); + + new GolangFlyweightGenerator(ir, false, outputManager).generate(); + + final Map sources = outputManager.getSources(); + final String source = sources.values().stream() + .map(CharSequence::toString) + .collect(Collectors.joining("\n")); + assertThat(source, containsString("float32(math.Inf(1))")); + assertThat(source, containsString("float32(math.Inf(-1))")); + assertThat(source, containsString("math.Inf(1)")); + assertThat(source, containsString("math.Inf(-1)")); + + final String floatConstantsSource = sources.values().stream() + .map(CharSequence::toString) + .filter(value -> value.contains("type FloatConstants struct")) + .findFirst() + .orElseThrow(); + assertThat(floatConstantsSource, containsString("\"math\"")); + assertThat(floatConstantsSource, containsString("math.Inf")); + } + } +} diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/golang/GolangGeneratorTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/golang/GolangGeneratorTest.java index f622f8081a..de102e9f37 100644 --- a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/golang/GolangGeneratorTest.java +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/golang/GolangGeneratorTest.java @@ -514,4 +514,36 @@ func LowerCaseValueRefInit(l *LowerCaseValueRef) { """)); } } + + @Test + void shouldGenerateFloatingPointInfinityLiterals() throws Exception + { + try (InputStream in = Tests.getLocalResource("floating-point-infinity-schema.xml")) + { + final ParserOptions options = ParserOptions.builder().stopOnError(true).build(); + final MessageSchema schema = parse(in, options); + final Ir ir = new IrGenerator().generate(schema); + final StringWriterOutputManager outputManager = new StringWriterOutputManager(); + outputManager.setPackageName(ir.applicableNamespace()); + + new GolangGenerator(ir, outputManager).generate(); + + final java.util.Map sources = outputManager.getSources(); + final String source = sources.values().stream() + .map(CharSequence::toString) + .collect(java.util.stream.Collectors.joining("\n")); + assertThat(source, containsString("float32(math.Inf(1))")); + assertThat(source, containsString("float32(math.Inf(-1))")); + assertThat(source, containsString("math.Inf(1)")); + assertThat(source, containsString("math.Inf(-1)")); + + final String floatConstantsSource = sources.values().stream() + .map(CharSequence::toString) + .filter(value -> value.contains("type FloatConstants struct")) + .findFirst() + .orElseThrow(); + assertThat(floatConstantsSource, containsString("\"math\"")); + assertThat(floatConstantsSource, containsString("math.Inf")); + } + } } diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/JavaGeneratorTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/JavaGeneratorTest.java index e5d4a1f5a1..fe9496453d 100644 --- a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/JavaGeneratorTest.java +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/JavaGeneratorTest.java @@ -257,6 +257,35 @@ void shouldGenerateBasicMessage() throws Exception assertThat(msgFlyweight.toString(), startsWith("[Car]")); } + @Test + void shouldGenerateFloatingPointInfinityLiterals() throws Exception + { + try (InputStream in = Tests.getLocalResource("floating-point-infinity-schema.xml")) + { + final ParserOptions options = ParserOptions.builder().stopOnError(true).build(); + final MessageSchema schema = parse(in, options); + ir = new IrGenerator().generate(schema); + + outputManager.clear(); + outputManager.setPackageName(ir.applicableNamespace()); + + generator().generate(); + new JavaDtoGenerator(ir, false, outputManager).generate(); + + final String source = outputManager.getSources().values().stream() + .map(CharSequence::toString) + .collect(java.util.stream.Collectors.joining("\n")); + + assertThat(source, allOf( + containsString("Float.POSITIVE_INFINITY"), + containsString("Double.NEGATIVE_INFINITY"))); + assertNotNull(CompilerUtil.compileInMemory( + ir.applicableNamespace() + ".InfinityValuesDecoder", outputManager.getSources())); + assertNotNull(CompilerUtil.compileInMemory( + ir.applicableNamespace() + ".InfinityValuesDto", outputManager.getSources())); + } + } + @Test void shouldGenerateWithoutPrecedenceChecksByDefault() throws Exception { diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/JavaUtilTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/JavaUtilTest.java new file mode 100644 index 0000000000..61e9de40a8 --- /dev/null +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/JavaUtilTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013-2025 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package uk.co.real_logic.sbe.generation.java; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static uk.co.real_logic.sbe.PrimitiveType.DOUBLE; +import static uk.co.real_logic.sbe.PrimitiveType.FLOAT; +import static uk.co.real_logic.sbe.PrimitiveType.INT32; +import static uk.co.real_logic.sbe.generation.java.JavaUtil.generateLiteral; + +class JavaUtilTest +{ + @Test + void shouldGenerateFloatingPointInfinityLiterals() + { + assertEquals("Float.POSITIVE_INFINITY", generateLiteral(FLOAT, "Infinity")); + assertEquals("Float.NEGATIVE_INFINITY", generateLiteral(FLOAT, "-Infinity")); + assertEquals("Double.POSITIVE_INFINITY", generateLiteral(DOUBLE, "Infinity")); + assertEquals("Double.NEGATIVE_INFINITY", generateLiteral(DOUBLE, "-Infinity")); + } + + @Test + void shouldPreserveExistingFloatingPointAndIntegerLiterals() + { + assertEquals("Float.NaN", generateLiteral(FLOAT, "NaN")); + assertEquals("Double.NaN", generateLiteral(DOUBLE, "NaN")); + assertEquals("1.5f", generateLiteral(FLOAT, "1.5")); + assertEquals("-1.5f", generateLiteral(FLOAT, "-1.5")); + assertEquals("-0.0f", generateLiteral(FLOAT, "-0.0")); + assertEquals("1.5d", generateLiteral(DOUBLE, "1.5")); + assertEquals("-1.5d", generateLiteral(DOUBLE, "-1.5")); + assertEquals("-0.0d", generateLiteral(DOUBLE, "-0.0")); + assertEquals("1", generateLiteral(INT32, "1")); + } +} diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/rust/RustGeneratorTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/rust/RustGeneratorTest.java new file mode 100644 index 0000000000..441d6b671b --- /dev/null +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/rust/RustGeneratorTest.java @@ -0,0 +1,67 @@ +/* + * Copyright 2013-2025 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package uk.co.real_logic.sbe.generation.rust; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import uk.co.real_logic.sbe.Tests; +import uk.co.real_logic.sbe.ir.Ir; +import uk.co.real_logic.sbe.xml.IrGenerator; +import uk.co.real_logic.sbe.xml.MessageSchema; +import uk.co.real_logic.sbe.xml.ParserOptions; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static uk.co.real_logic.sbe.xml.XmlSchemaParser.parse; + +class RustGeneratorTest +{ + @TempDir + Path tempDir; + + @Test + void shouldGenerateFloatingPointInfinityLiteralsForConstantsAndNulls() throws Exception + { + try (InputStream in = Tests.getLocalResource("floating-point-infinity-schema.xml")) + { + final ParserOptions options = ParserOptions.builder().stopOnError(true).build(); + final MessageSchema schema = parse(in, options); + final Ir ir = new IrGenerator().generate(schema); + final RustOutputManager outputManager = new RustOutputManager( + tempDir.toString(), ir.packageName()); + + new RustGenerator(ir, "0.1.0", outputManager).generate(); + + final Path sourceDirectory = tempDir.resolve(ir.packageName()).resolve("src"); + final String constants = Files.readString(sourceDirectory.resolve("float_constants_codec.rs")); + final String values = Files.readString(sourceDirectory.resolve("infinity_values_codec.rs")); + + assertThat(constants, allOf( + containsString("f32::INFINITY"), + containsString("-f32::INFINITY"))); + assertThat(values, allOf( + containsString("f32::INFINITY"), + containsString("-f32::INFINITY"), + containsString("f64::INFINITY"), + containsString("-f64::INFINITY"))); + } + } +} diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/rust/RustUtilTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/rust/RustUtilTest.java index e86e54c438..28be49b923 100644 --- a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/rust/RustUtilTest.java +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/rust/RustUtilTest.java @@ -68,8 +68,16 @@ void generateRustLiteralsHappyPaths() assertEquals("65_u8", generateRustLiteral(CHAR, "65")); assertEquals("64.1_f64", generateRustLiteral(DOUBLE, "64.1")); assertEquals("f64::NAN", generateRustLiteral(DOUBLE, "NaN")); + assertEquals("f64::INFINITY", generateRustLiteral(DOUBLE, "Infinity")); + assertEquals("-f64::INFINITY", generateRustLiteral(DOUBLE, "-Infinity")); assertEquals("64.1_f32", generateRustLiteral(FLOAT, "64.1")); + assertEquals("-1.5_f32", generateRustLiteral(FLOAT, "-1.5")); + assertEquals("-0.0_f32", generateRustLiteral(FLOAT, "-0.0")); assertEquals("f32::NAN", generateRustLiteral(FLOAT, "NaN")); + assertEquals("f32::INFINITY", generateRustLiteral(FLOAT, "Infinity")); + assertEquals("-f32::INFINITY", generateRustLiteral(FLOAT, "-Infinity")); + assertEquals("-1.5_f64", generateRustLiteral(DOUBLE, "-1.5")); + assertEquals("-0.0_f64", generateRustLiteral(DOUBLE, "-0.0")); assertEquals("65_i8", generateRustLiteral(INT8, "65")); assertEquals("65_i16", generateRustLiteral(INT16, "65")); diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/ir/EncodedIrTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/ir/EncodedIrTest.java index a8daf9bd2a..6affa5e032 100644 --- a/sbe-tool/src/test/java/uk/co/real_logic/sbe/ir/EncodedIrTest.java +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/ir/EncodedIrTest.java @@ -172,6 +172,12 @@ void shouldDecodeMessagesAndTypes() throws Exception testDecodeTypes("code-generation-schema.xml"); } + @Test + void shouldRoundTripFloatingPointInfinityValues() throws Exception + { + testDecodeTypes("floating-point-infinity-schema.xml"); + } + @Test void shouldPreservePackageNames() throws Exception { diff --git a/sbe-tool/src/test/resources/floating-point-infinity-schema.xml b/sbe-tool/src/test/resources/floating-point-infinity-schema.xml new file mode 100644 index 0000000000..a7b385efde --- /dev/null +++ b/sbe-tool/src/test/resources/floating-point-infinity-schema.xml @@ -0,0 +1,43 @@ + + + + + + + + + + Infinity + -Infinity + Infinity + -Infinity + + + + + + + + + + + + + + + + + + + +