mirror of
https://github.com/abue-ammar/tinycast.git
synced 2026-10-02 08:14:38 +08:00
Read thousand, million and billion after a calculator amount (#1182)
This commit is contained in:
@@ -56,6 +56,26 @@ struct CalcTests {
|
||||
expectDisplay("10k * 2", "20,000")
|
||||
expectBadges("10k", source: "Expression", target: "Result")
|
||||
|
||||
// Magnitude words scale the literal before them, spaced or attached, in any case
|
||||
expectDisplay("13 million", "13,000,000")
|
||||
expectCopy("1.5 billion", "1500000000")
|
||||
expectDisplay("2 Thousand + 1", "2,001")
|
||||
expectDisplay("3million / 2", "1,500,000")
|
||||
expectDisplay("13 million idr to usd", "720.10 USD")
|
||||
expectDisplay("1.5 million idr to sgd", "112.17 SGD")
|
||||
expectDisplay("IDR 13 million to usd", "720.10 USD")
|
||||
expectDisplay("10% of 2 million", "200,000")
|
||||
expectExpression("13 million +", "13 million +")
|
||||
expectBadges("13 million", source: "Expression", target: "Result")
|
||||
expectNil("million")
|
||||
expectNil("2 * million")
|
||||
expectNil("(2 + 3) million")
|
||||
expectNil("13 millions")
|
||||
expectNil("13 million2")
|
||||
expectNil("1e308 billion")
|
||||
expectDisplay("10 milliseconds to s", "0.01 s")
|
||||
expectLocalized("1,5 million", "1.500.000", italian)
|
||||
|
||||
// Scientific notation input
|
||||
expectDisplay("1e6 + 1", "1,000,001")
|
||||
expectDisplay("1.5e-3 * 2", "0.003")
|
||||
|
||||
@@ -2,7 +2,7 @@ import Foundation
|
||||
|
||||
enum CalcToken: Equatable, Sendable {
|
||||
case number(Double)
|
||||
/// Shorthand (`10k`, `1e5`), kept distinct so a lone one still earns a card.
|
||||
/// Shorthand (`10k`, `1e5`, `2 million`), kept distinct so a lone one still earns a card.
|
||||
case compactNumber(Double)
|
||||
/// Radix-prefixed integer literal (0xff / 0b1010 / 0o777), kept exact for base conversion.
|
||||
case intLiteral(UInt64, base: CalcNumberBase)
|
||||
|
||||
@@ -75,6 +75,11 @@ enum CalcTokenizer {
|
||||
guard scaled.isFinite else { return nil }
|
||||
tokens.append(.compactNumber(scaled))
|
||||
i += 1
|
||||
} else if let magnitude = magnitudeWord(chars, after: i) {
|
||||
let scaled = value * magnitude.scale
|
||||
guard scaled.isFinite else { return nil }
|
||||
tokens.append(.compactNumber(scaled))
|
||||
i = magnitude.end
|
||||
} else if isShorthand {
|
||||
tokens.append(.compactNumber(value))
|
||||
} else {
|
||||
@@ -260,6 +265,24 @@ enum CalcTokenizer {
|
||||
return (name, end)
|
||||
}
|
||||
|
||||
/// Short scale only: the engine reads canonical English, where a billion is 10⁹.
|
||||
private static let magnitudes: [String: Double] = ["thousand": 1e3, "million": 1e6, "billion": 1e9]
|
||||
|
||||
/// The scale of a whole magnitude word following a literal (`13 million`), and where it ends.
|
||||
private static func magnitudeWord(
|
||||
_ chars: [Unicode.Scalar], after index: Int
|
||||
) -> (scale: Double, end: Int)? {
|
||||
var start = index
|
||||
while start < chars.count, chars[start].isWhitespace { start += 1 }
|
||||
var end = start
|
||||
while end < chars.count, chars[end].isLetter { end += 1 }
|
||||
guard end > start else { return nil }
|
||||
if end < chars.count, chars[end].isNumber || chars[end].isCombiningMark { return nil }
|
||||
guard let scale = magnitudes[String(String.UnicodeScalarView(chars[start..<end])).lowercased()]
|
||||
else { return nil }
|
||||
return (scale, end)
|
||||
}
|
||||
|
||||
/// Whether the `k` at `index` is a thousands suffix rather than Kelvin or a unit's head.
|
||||
private static func isCompactSuffix(_ chars: [Unicode.Scalar], _ index: Int) -> Bool {
|
||||
let next = index + 1
|
||||
|
||||
@@ -264,6 +264,12 @@ An attached `k` is a thousands suffix (`10k` → `10,000`), while whitespace kee
|
||||
(`10 k to c`); the established attached Kelvin conversion form remains valid when the temperature
|
||||
target makes the intent unambiguous (`273.15K to C`).
|
||||
|
||||
A whole magnitude word after a literal scales it the same way, spaced or attached and in any case:
|
||||
`thousand`, `million` and `billion` (`13 million idr to usd`, `1.5 billion`). The set is closed and
|
||||
short-scale — a billion is 10⁹, since the engine reads canonical English. It scales only the literal it
|
||||
follows, so `2 * million` and `(2 + 3) million` stay silent, and plurals (`13 millions`) are not read.
|
||||
Abbreviations are deliberately absent: `m`, `b` and `k` already mean metre, byte and Kelvin.
|
||||
|
||||
A **compound unit** (`km/h`, `m³/h`, `mbit/s`, `fl oz`) stays whole only when the table knows its
|
||||
spelling: the tokenizer looks ahead across `/` or whitespace between two alphanumeric runs,
|
||||
folds superscript powers, and keeps them together only if `CalcUnits.byName` resolves the result.
|
||||
|
||||
Reference in New Issue
Block a user