Number_types: fix Lazy_exact_nt operator<< to write exact() value - #9431
Number_types: fix Lazy_exact_nt operator<< to write exact() value#9431RajdeepKushwaha5 wants to merge 9 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes Lazy_exact_nt<ET> stream output so that saving/loading exact values round-trips without precision loss, addressing issue #135 in the Number_types package.
Changes:
- Update
operator<<forLazy_exact_nt<ET>to writea.exact()instead ofto_double(a). - Update documentation to describe the new exact output behavior.
- Add and register a regression test that exercises round-trip I/O for several rational/integer cases.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| Number_types/include/CGAL/Lazy_exact_nt.h | Changes streaming output to use the exact value (exact()), enabling lossless round-trip with existing operator>>. |
| Number_types/doc/Number_types/CGAL/Lazy_exact_nt.h | Updates docs to match the new exact-output semantics and round-trip intent. |
| Number_types/test/Number_types/Lazy_exact_nt_io.cpp | Adds regression coverage for round-trip I/O of exact rationals/integers (including large values). |
| Number_types/test/Number_types/CMakeLists.txt | Registers the new single-source test program. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| std::istringstream iss(oss.str()); | ||
| Lazy_nt b; | ||
| iss >> b; | ||
| assert(a == b); |
There was a problem hiding this comment.
Same issue as above: the test should assert the stream extraction succeeded after iss >> b to avoid false positives if parsing fails.
| std::istringstream iss(oss.str()); | ||
| Lazy_nt b; | ||
| iss >> b; | ||
| assert(a == b); |
There was a problem hiding this comment.
Same issue as above: please assert the stream is still good after iss >> b so the test can’t pass if parsing fails.
| oss << c; | ||
| std::istringstream iss(oss.str()); | ||
| Lazy_nt d; | ||
| iss >> d; |
There was a problem hiding this comment.
Same issue as above: add a stream-state assertion after iss >> d to ensure the read succeeded before comparing c == d.
| iss >> d; | |
| iss >> d; | |
| assert(iss); |
| std::istringstream iss(oss.str()); | ||
| Lazy_nt b; | ||
| iss >> b; | ||
| assert(a == b); |
There was a problem hiding this comment.
Same issue as above: assert successful extraction after iss >> b so the test can’t pass if parsing fails.
| oss << a; | ||
| std::istringstream iss(oss.str()); | ||
| Lazy_nt b; | ||
| iss >> b; |
There was a problem hiding this comment.
Same issue as above: because the expected value is 0 here, a failed parse could leave b at the default value and make the assertion pass. Add an assert(iss) / assert(!iss.fail()) after iss >> b.
| iss >> b; | |
| iss >> b; | |
| assert(iss); |
| std::istringstream iss(oss.str()); | ||
| Lazy_nt b; | ||
| iss >> b; | ||
| assert(a == b); |
There was a problem hiding this comment.
The test doesn’t verify that extraction succeeded (stream state) before comparing values. If parsing fails, b can remain at its default value (which is 0 for Lazy_exact_nt), potentially making the test pass spuriously (notably in the zero case). Add an assertion like assert(iss) / assert(!iss.fail()) after iss >> b (and similarly in the other test blocks).
56d312f to
a935007
Compare
Change operator<< for Lazy_exact_nt to output a.exact() instead of to_double(a). The old to_double() conversion loses precision and breaks round-trip save/load of exact kernel coordinates (issue CGAL#135). The corresponding operator>> already uses read_float_or_quotient() which handles both floating-point and rational (n/d) formats, so no changes are needed on the input side. Add Lazy_exact_nt_io.cpp regression test verifying round-trip I/O for rationals, integers, negative values, computed sums, large values, and zero.
…stream state Addresses review feedback on PR 9431: - Replace CGAL::Gmpq/Gmpz with CGAL::Exact_rational so the test compiles without GMP (per afabri's comment). - Add assert(iss) after every stream extraction to fail loudly on parse errors (per Copilot AI review).
78773d6 to
5013864
Compare
|
This is not a breaking change I'm willing to have. The only option I would validate is the stream modifier solution suggested by Laurent in the issue. |
…t_mode Following review, operator<< no longer changes the default output, which would break existing code that relies on the double output. It keeps to_double() by default, and a per-stream flag set by CGAL::IO::set_exact_mode() switches it to write exact() for a lossless round-trip with operator>> (issue CGAL#135). This mirrors set_pretty_mode() (std::ios xalloc/iword). The test sets the mode for the round-trip checks and adds a case confirming the default still writes a double.
|
I have reworked as discussed. |
fallenmi
left a comment
There was a problem hiding this comment.
set_exact_mode() is not lossless for every supported Lazy_exact_nt backend. The new branch delegates to a.exact()'s stream operator, but CORE::Expr::operator<< writes a decimal approximation. CORE::Expr is a shipped CGAL number type, and the existing Number_types suite instantiates Lazy_exact_nt<CORE::Expr> in its CORE+LEDA configuration.
I reproduced this on exact head a3c59ff and current merge 54a86f0 with the exact value 1/3. At stream precisions 6, 18, and 80, exact mode wrote 0.333333, 0.33333333333333333, and 0.33333333333333333333333333; every value parsed successfully, but every restored value compared unequal to the original exact value. In contrast, the submitted Exact_rational path writes 1/3 and round-trips correctly, and the historical default remains 0.333333.
Please either make exact-mode serialization round-trip for the supported exact backends, or constrain/guard the API and documentation to the backends for which that guarantee is valid, and add a CORE::Expr regression. The exact-head CI checks are otherwise green; the new focused target and the existing ioformat/Exact_rational tests pass locally.
Reviewed with OpenAI Codex assistance; I independently traced the supported backend stream operators and reproduced the base, exact-head, and current-merge behavior locally.
…-serializable backends Following review, set_exact_mode() is lossless only for exact types whose own operator<< writes an exact representation, such as rational types (Exact_rational, Gmpq). For CORE::Expr, whose operator<< writes a decimal approximation, exact mode is not lossless. The documentation (reference header and inline) now states this guarantee precisely instead of claiming universal losslessness, and the test adds a CORE::Expr regression (guarded by CGAL_USE_CORE) checking the default stays a double and exact mode writes exact()'s representation which parses.
… scoped guarantee
|
Thank you for the careful review and the reproduction, you were right. I confirmed it on a CORE build: I have scoped the guarantee accordingly: the reference documentation and the inline comments now state that exact mode is lossless only when |
| #include <CGAL/Lazy_exact_nt.h> | ||
| #include <CGAL/Exact_rational.h> | ||
|
|
||
| #ifdef CGAL_USE_CORE |
There was a problem hiding this comment.
no need for that macro anymore, it is always available
There was a problem hiding this comment.
Done, removed the CGAL_USE_CORE guard so the CORE::Expr test is unconditional. It builds and passes, and it is registered the same way as the other CORE tests.
Thank you.
|
The behavior I see in the test is what I would have expect. Let's test it. |
…s available (review)
fallenmi
left a comment
There was a problem hiding this comment.
Thanks — this addresses my prior blocker. At exact head fbc2039575c9e40fafbe2a8063317731af0213d3, the API and documentation now scope the lossless guarantee to backends with an exact stream representation, and the CORE::Expr regression is unconditional as requested by the maintainer.
I rechecked the submitted nine cases and the focused three-precision CORE oracle on this head and the current clean merge 330f425a07296152f9d406fc1a7c9a87609d8b3d; parsing succeeds and the documented lossy CORE behavior is preserved, while Exact_rational still round-trips exactly. The changed header, documentation, and test blobs are identical in the merge. All eight substantive check runs are successful; the remaining check is an intentional skipped label-removal job.
Disclosure: I used OpenAI Codex to assist this changed-head review. I verified the exact commits, current merge, source paths, interactions, and live CI before submitting.
|
You still have to add the functions in the package |
…tream Support Following review, CGAL::IO::set_exact_mode, set_lossy_mode and is_exact_mode are moved from Number_types/Lazy_exact_nt.h into Stream_support's IO/io.h, next to the other stream mode manipulators, and reuse the existing Static flag-index idiom. They are documented there with \ingroup PkgStreamSupportRef and CGAL::IO prefixed \sa links, and are listed in the Stream Support PackageDescription, so the documentation testsuite no longer reports them as missing. Lazy_exact_nt's operator<< keeps calling CGAL::IO::is_exact_mode; Lazy_exact_nt.h already includes CGAL/IO/io.h. Behaviour and the test are unchanged.
|
I have moved |
|
/build:v0 |
|
The documentation is built. It will be available, after a few minutes, here: https://cgal.github.io/9431/v0/Manual/index.html |
|
I am surprised that in the documentation of Concerning the lossy mode, I am wondering if we want that only for As the link to |
… per type The documentation of set_exact_mode() and set_lossy_mode() links to CGAL::Lazy_exact_nt and CGAL::Gmpq, which live in Number_types, so add Number_types to the doc dependencies of Stream_support for those links to resolve. Also state explicitly which types consult the flag and what the default is for each: the flag is only read by number types whose default output is an approximation, currently CGAL::Lazy_exact_nt, while non lazy exact types such as CGAL::Gmpq always write their exact representation and ignore it.
|
@afabri Thanks for the review. Pushed in 8ccae13. Number_types in Added. The documentation of The default, and whether the mode should apply to other exact types You are right that the default was not stated per type. I have now documented it explicitly on both functions: the flag is only consulted by number types whose default output is an approximation, which today means
On whether to extend the mode, my suggestion is to keep it to lazy types in this PR. Documentation of the You are right, One process question Since the manipulators now live in Stream Support, this PR adds three public functions rather than only fixing |
|
Successfully tested in CGAL-6.3-Ic-73 |
|
@RajdeepKushwaha5 I think it would be better to write a small feature page on the wiki so that naming could be discussed and approve following our guidelines. |
Summary of Changes
operator<<forLazy_exact_nt<ET>writesto_double(a), which loses precision and breaks round-trip save and load of exact kernel coordinates. WritingLazy_exact_nt<Exact_rational>(1/3)outputs0.333333, and reading it back gives adifferent exact value. This is issue #135.
Changing that unconditionally is a breaking change, so following the stream modifier solution suggested by Laurent in the issue and requested by @sloriot, the default is left untouched and the exact output is opt-in per stream:
The three manipulators live in Stream Support next to the other stream mode manipulators and reuse the same
std::ios::xallocandiwordidiom asset_pretty_mode.The lossless round-trip holds when
EThas an exact stream representation, such as a rational type. It does not hold for anETwhose own output operator writes an approximation, for exampleCORE::Expr, which writes a decimal. That limit isdocumented and covered by a test.
Changes
Stream_support/include/CGAL/IO/io.h: addedCGAL::IO::set_exact_mode(),CGAL::IO::set_lossy_mode()andCGAL::IO::is_exact_mode(), documented with\ingroup PkgStreamSupportRef.Stream_support/doc/Stream_support/PackageDescription.txt: registered the three functions.Stream_support/doc/Stream_support/dependencies: addedNumber_typesso theCGAL::Lazy_exact_ntandCGAL::Gmpqlinks resolve.Number_types/include/CGAL/Lazy_exact_nt.h:operator<<writesa.exact()when the stream is in exact mode andto_double(a)otherwise, which stays the default.Number_types/doc/Number_types/CGAL/Lazy_exact_nt.h: documented the behaviour and the limits of the round-trip guarantee.Number_types/test/Number_types/Lazy_exact_nt_io.cppandCMakeLists.txt: nine cases covering rationals, integers, negatives, computed sums, large values, zero, the unchanged default,set_lossy_modewith the previous state return of thesetters, and the
CORE::Exprbehaviour.Release Management
CGAL::IO::set_exact_mode(),CGAL::IO::set_lossy_mode()andCGAL::IO::is_exact_mode()