Exercism: Armstrong Numbers, failed test

Hi,

I'm trying to solve this exercise on Exercism. Unfortunately, I don't passed 1 tests: thread 'test_properly_handles_overflow' panicked at 'assertion failed: !is_armstrong_number(4_106_098_957)'. I don't know what I am doing incorrectly.

lib.rs:

use num_traits::pow;
use std::num::Wrapping;

pub fn is_armstrong_number(num: u32) -> bool {
    let mut digits = Vec::new();
    let mut n = num;
    while n > 9 {
        digits.push(n % 10);
        n = n / 10;
    }
    digits.push(n);
    
    let mut total = Wrapping(0u32);
    let mut sum = Wrapping(0u32);
    for i in 0..digits.len() {
        sum += Wrapping(pow(digits[i], digits.len()));
        total += Wrapping(digits[i] * pow(10, i));
    }
    if sum == total {
        return true;
    }
    false
}



#[test]
fn test_zero_is_an_armstrong_number() {
    assert!(is_armstrong_number(0))
}
#[test]
fn test_single_digit_numbers_are_armstrong_numbers() {
    assert!(is_armstrong_number(5))
}
#[test]
fn test_there_are_no_2_digit_armstrong_numbers() {
    assert!(!is_armstrong_number(10))
}
#[test]
fn test_three_digit_armstrong_number() {
    assert!(is_armstrong_number(153))
}
#[test]
fn test_three_digit_non_armstrong_number() {
    assert!(!is_armstrong_number(100))
}
#[test]
fn test_four_digit_armstrong_number() {
    assert!(is_armstrong_number(9474))
}
#[test]
fn test_four_digit_non_armstrong_number() {
    assert!(!is_armstrong_number(9475))
}
#[test]
fn test_seven_digit_armstrong_number() {
    assert!(is_armstrong_number(9_926_315))
}
#[test]
fn test_seven_digit_non_armstrong_number() {
    assert!(!is_armstrong_number(9_926_316))
}
#[test]
fn test_nine_digit_armstrong_number() {
    assert!(is_armstrong_number(912_985_153));
}
#[test]
fn test_nine_digit_non_armstrong_number() {
    assert!(!is_armstrong_number(999_999_999));
}
#[test]
fn test_ten_digit_non_armstrong_number() {
    assert!(!is_armstrong_number(3_999_999_999));
}
// The following number has an Armstrong sum equal to 2^32 plus itself,
// and therefore will be detected as an Armstrong number if you are
// incorrectly using wrapping arithmetic.
#[test]
fn test_properly_handles_overflow() {
    assert!(!is_armstrong_number(4_106_098_957));
}

cargo.toml:

[package]
name = "armstrong_test"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
num-traits = "0.2"

4_106_098_957 makes me suspicious because the max for u32 is about 4 billion. (Looks like that value works out to be 0xF4BE_190D.)

We see you're using Wrapping everywhere. Do you really want wrapping here? Why not use normal math, run it it debug, and see if things are overflowing? If it does, what if you use u64 instead of u32? Should you maybe use checked_add and such instead in places, and just fail on overflows?

2 Likes

I'm confused, isn't the comment here saying what's happening?

// The following number has an Armstrong sum equal to 2^32 plus itself,
// and therefore will be detected as an Armstrong number if you are
// incorrectly using wrapping arithmetic.

So it's explicit that the test will fail if you're using wrapping arithmetic that this test is checking that you're not. So... don't use Wrapping?

It seems like the intention is to return false for any numbers that would overflow, which seems a bit un-rusty. You can replace all your operations with checked_ methods like u32 - Rust, which return None for out of range values, and use them something like:

// Returns None if the value goes out of range
pub fn is_armstrong_number(num: u32) -> Option<bool> {
...

// Will bail with None if anything gets out of range, due to the ?s.
sum = sum.checked_add(digits[i].checked_pow(digits.len())?)?;

(Wrote this on my phone, so probably wrong!)

But this isn't normal Rust code, which would just let it panic when your values get out of range in debug. For that test, perhaps mark it as #[should_panic] and remove the assert (since otherwise you're just testing the assert fails)

1 Like

What do you mean by “seems a bit un-rusty”? It’s literally the correct answer to return false. The sum is getting larger than u32::MAX, while the number was a u32, so they cannot possibly be the same.

The exercise doesn’t convey any intention (in terms of what code you ought to write), it just requires a solution that gives correct results for all u32 inputs, and they included a test-case that’s good at detecting a certain kind of erroneous implementation, i.e. one that uses wrapping u32 arithmetic without paying attention to overflows. Presumably, using a sufficiently large integer type instead would be a solution that’s just as good/“intended” here.

4 Likes

Fair enough: I didn't want to risk learning some maths and finding out what Armstrong numbers actually were though :yum:

1 Like

You are right @scottmcm I could use u64. By using u64 all tests passed:

use num_traits::pow;

pub fn is_armstrong_number(num: u32) -> bool {
let mut digits = Vec::new();
let mut n = num;
while n > 9 {
digits.push(n % 10);
n = n / 10;
}
digits.push(n);

let mut total = 0;
let mut sum = 0;
for i in 0..digits.len() {
    sum += pow(digits[i], digits.len()) as u64;
    total += (digits[i] * pow(10, i)) as u64;
}
if sum == total {
    return true;
}

false
}

Thank you all for your help.