Skip to content

Commit a9f9b5c

Browse files
youniespedberg-icu
authored andcommitted
ICU-23104 Fix the handling of part-per-1e9
See #3663
1 parent f692d3c commit a9f9b5c

File tree

4 files changed

+81
-55
lines changed

4 files changed

+81
-55
lines changed

icu4c/source/i18n/measunit_extra.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,21 +1418,21 @@ MeasureUnit MeasureUnit::withPrefix(UMeasurePrefix prefix,
14181418
}
14191419

14201420
uint64_t MeasureUnit::getConstantDenominator(UErrorCode &status) const {
1421-
auto complexity = this->getComplexity(status);
1421+
// TODO(ICU-23219)
1422+
auto measureUnitImpl = MeasureUnitImpl::forMeasureUnitMaybeCopy(*this, status);
14221423
if (U_FAILURE(status)) {
14231424
return 0;
14241425
}
14251426

1427+
auto complexity = measureUnitImpl.complexity;
1428+
14261429
if (complexity != UMEASURE_UNIT_SINGLE && complexity != UMEASURE_UNIT_COMPOUND) {
14271430
status = U_ILLEGAL_ARGUMENT_ERROR;
14281431
return 0;
14291432
}
14301433

1431-
if (this->fImpl == nullptr) {
1432-
return 0;
1433-
}
14341434

1435-
return this->fImpl->constantDenominator;
1435+
return measureUnitImpl.constantDenominator;
14361436
}
14371437

14381438
MeasureUnit MeasureUnit::withConstantDenominator(uint64_t denominator, UErrorCode &status) const {
@@ -1501,8 +1501,8 @@ MeasureUnit MeasureUnit::product(const MeasureUnit& other, UErrorCode& status) c
15011501
impl.appendSingleUnit(*otherImpl.singleUnits[i], status);
15021502
}
15031503

1504-
uint64_t currentConstatDenominator = this->getConstantDenominator(status);
1505-
uint64_t otherConstantDenominator = other.getConstantDenominator(status);
1504+
uint64_t currentConstatDenominator = impl.constantDenominator;
1505+
uint64_t otherConstantDenominator = otherImpl.constantDenominator;
15061506

15071507
// TODO: we can also multiply the constant denominators instead of returning an error.
15081508
if (currentConstatDenominator != 0 && otherConstantDenominator != 0) {

icu4c/source/test/intltest/numbertest_skeletons.cpp

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "unicode/dcfmtsym.h"
99
#include "unicode/ustring.h"
10+
#include "unicode/unistr.h"
1011

1112
#include "cstr.h"
1213
#include "numbertest.h"
@@ -467,28 +468,35 @@ void NumberSkeletonTest::perUnitToSkeleton() {
467468
{u"area", u"acre"},
468469
{u"concentr", u"percent"},
469470
{u"concentr", u"permille"},
470-
{u"concentr", u"part-per-1e6"},
471471
{u"concentr", u"permyriad"},
472472
{u"digital", u"bit"},
473473
{u"length", u"yard"},
474+
{u"concentr", u"part-per-1e9"},
474475
};
475476

476-
for (const auto& cas1 : cases) {
477-
for (const auto& cas2 : cases) {
477+
for (const auto& numeratorCase : cases) {
478+
for (const auto& denominatorCase : cases) {
478479
UnicodeString skeleton(u"measure-unit/");
479-
skeleton += cas1.type;
480+
skeleton += numeratorCase.type;
480481
skeleton += u"-";
481-
skeleton += cas1.subtype;
482+
skeleton += numeratorCase.subtype;
482483
skeleton += u" ";
483484
skeleton += u"per-measure-unit/";
484-
skeleton += cas2.type;
485+
skeleton += denominatorCase.type;
485486
skeleton += u"-";
486-
skeleton += cas2.subtype;
487+
skeleton += denominatorCase.subtype;
488+
489+
// Convert UTF-16 string to UTF-8 for forIdentifier
490+
UnicodeString temp(denominatorCase.subtype);
491+
CStr utf8String(temp);
492+
MeasureUnit denominatorUnit = MeasureUnit::forIdentifier(utf8String(), status);
493+
494+
// If the units has a constant denominator, we skip the test because we could not have a per unit with a constant denominator.
495+
if (denominatorUnit.getConstantDenominator(status) != 0) {
496+
continue;
497+
}
487498

488499
status.setScope(skeleton);
489-
if (u_strcmp(cas1.subtype, u"part-per-1e6")==0 || u_strcmp(cas2.subtype, u"part-per-1e6")==0) {
490-
logKnownIssue("ICU-23104", "Strange handling of part-per-1e9 & volumes in skeletons");
491-
} else if (cas1.type != cas2.type && cas1.subtype != cas2.subtype) {
492500
UnicodeString toSkeleton = NumberFormatter::forSkeleton(
493501
skeleton, status).toSkeleton(status);
494502
if (status.errIfFailureAndReset()) {
@@ -498,19 +506,18 @@ void NumberSkeletonTest::perUnitToSkeleton() {
498506
UnicodeString msg;
499507
msg.append(toSkeleton)
500508
.append(" should contain '")
501-
.append(UnicodeString(cas1.subtype))
509+
.append(UnicodeString(numeratorCase.subtype))
502510
.append("' when constructed from ")
503511
.append(skeleton);
504-
assertTrue(msg, toSkeleton.indexOf(cas1.subtype) >= 0);
512+
assertTrue(msg, toSkeleton.indexOf(numeratorCase.subtype) >= 0);
505513

506514
msg.remove();
507515
msg.append(toSkeleton)
508516
.append(" should contain '")
509-
.append(UnicodeString(cas2.subtype))
517+
.append(UnicodeString(denominatorCase.subtype))
510518
.append("' when constructed from ")
511519
.append(skeleton);
512-
assertTrue(msg, toSkeleton.indexOf(cas2.subtype) >= 0);
513-
}
520+
assertTrue(msg, toSkeleton.indexOf(denominatorCase.subtype) >= 0);
514521
}
515522
}
516523
}

icu4j/main/core/src/main/java/com/ibm/icu/util/MeasureUnit.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -602,16 +602,17 @@ public MeasureUnit withConstantDenominator(long denominator) {
602602
* @draft ICU 77
603603
*/
604604
public long getConstantDenominator() {
605-
if (this.getComplexity() != Complexity.COMPOUND && this.getComplexity() != Complexity.SINGLE) {
605+
// TODO(ICU-23219)
606+
MeasureUnitImpl measureUnitImpl = getCopyOfMeasureUnitImpl();
607+
608+
if (measureUnitImpl.getComplexity() != Complexity.COMPOUND
609+
&& measureUnitImpl.getComplexity() != Complexity.SINGLE) {
606610
throw new UnsupportedOperationException(
607611
"Constant denominator is only supported for COMPOUND & SINGLE units");
608612
}
609613

610-
if (this.measureUnitImpl == null) {
611-
return 0;
612-
}
613614

614-
return this.measureUnitImpl.getConstantDenominator();
615+
return measureUnitImpl.getConstantDenominator();
615616
}
616617

617618
/**
@@ -716,8 +717,8 @@ public MeasureUnit product(MeasureUnit other) {
716717
implCopy.appendSingleUnit(singleUnit);
717718
}
718719

719-
long thisConstantDenominator = this.getConstantDenominator();
720-
long otherConstantDenominator = other.getConstantDenominator();
720+
long thisConstantDenominator = implCopy.getConstantDenominator();
721+
long otherConstantDenominator = otherImplRef.getConstantDenominator();
721722

722723
// TODO: we can also multiply the constant denominators instead of throwing an
723724
// exception.

icu4j/main/core/src/test/java/com/ibm/icu/dev/test/number/NumberSkeletonTest.java

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.ibm.icu.number.LocalizedNumberFormatter;
1212
import com.ibm.icu.number.NumberFormatter;
1313
import com.ibm.icu.number.SkeletonSyntaxException;
14+
import com.ibm.icu.util.MeasureUnit;
1415
import com.ibm.icu.util.ULocale;
1516

1617
/**
@@ -428,35 +429,52 @@ public void perUnitInArabic() {
428429

429430
@Test
430431
public void perUnitToSkeleton() {
431-
String[][] cases = {
432-
{"area", "acre"},
433-
{"concentr", "percent"},
434-
{"concentr", "permille"},
435-
{"concentr", "part-per-1e9"},
436-
{"concentr", "permyriad"},
437-
{"digital", "bit"},
438-
{"length", "yard"},
432+
class TestCase {
433+
String type;
434+
String subType;
435+
436+
TestCase(String type, String sub_type) {
437+
this.type = type;
438+
this.subType = sub_type;
439+
}
440+
}
441+
442+
TestCase[] cases = new TestCase[] {
443+
new TestCase("area", "acre"),
444+
new TestCase("concentr", "percent"),
445+
new TestCase("concentr", "permille"),
446+
new TestCase("concentr", "permyriad"),
447+
new TestCase("digital", "bit"),
448+
new TestCase("length", "yard"),
449+
new TestCase("concentr", "part-per-1e9"),
439450
};
440451

441-
for (String[] cas1 : cases) {
442-
for (String[] cas2 : cases) {
443-
String skeleton = "measure-unit/" + cas1[0] + "-" + cas1[1] + " per-measure-unit/" +
444-
cas2[0] + "-" + cas2[1];
445-
446-
if (cas1[1].equals("part-per-1e9") || cas2[1].equals("part-per-1e9")) {
447-
logKnownIssue("ICU-23104", "Strange handling of part-per-1e9 in skeletons");
448-
} else if (cas1[0] != cas2[0] && cas1[1] != cas2[1]) {
449-
String toSkeleton = NumberFormatter.forSkeleton(skeleton).toSkeleton();
450-
451-
// Ensure both subtype are in the toSkeleton.
452-
String msg;
453-
msg = toSkeleton + " should contain '" + cas1[1] + "' when constructed from " +
454-
skeleton;
455-
assertTrue(msg, toSkeleton.indexOf(cas1[1]) >= 0);
456-
msg = toSkeleton + " should contain '" + cas2[1] + "' when constructed from " +
457-
skeleton;
458-
assertTrue(msg, toSkeleton.indexOf(cas2[1]) >= 0);
452+
for (TestCase numeratorCase : cases) {
453+
for (TestCase denominatorCase : cases) {
454+
MeasureUnit denominatorUnit = MeasureUnit.forIdentifier(denominatorCase.subType);
455+
456+
// If the units has a constant denominator, we skip the test because we could
457+
// not have a per unit with a constant denominator.
458+
if (denominatorUnit.getConstantDenominator() != 0) {
459+
continue;
459460
}
461+
462+
String skeleton = "measure-unit/" + numeratorCase.type + "-" + numeratorCase.subType
463+
+ " per-measure-unit/" +
464+
denominatorCase.type + "-" + denominatorCase.subType;
465+
String toSkeleton = NumberFormatter.forSkeleton(skeleton).toSkeleton();
466+
467+
468+
// Ensure both subtype (Units Cldr IDs) are in the toSkeleton.
469+
String skeletonMsgNumString = toSkeleton + " should contain '" + numeratorCase.subType
470+
+ "' when constructed from " +
471+
skeleton;
472+
assertTrue(skeletonMsgNumString, toSkeleton.indexOf(numeratorCase.subType) >= 0);
473+
474+
String skeletonMsgDenString = toSkeleton + " should contain '" + denominatorCase.subType
475+
+ "' when constructed from " +
476+
skeleton;
477+
assertTrue(skeletonMsgDenString, toSkeleton.indexOf(denominatorCase.subType) >= 0);
460478
}
461479
}
462480
}

0 commit comments

Comments
 (0)